• 18
    いいね
  • 0
    コメント

この記事は、C++ Advent Calendar 2016の17日目です。

こんにちは。聖夜も来週に迫ってまいりました今日このごろ、皆様に置かれましてはどうお過ごしでしょうか。私はAdCの締切に追われています。

さて、先日もアドベントカレンダーに一本記事を書かせていただきましたが、今回は初心者向けカレンダーではないので初心者向けではない内容を書こうと思います。
いよいよ来年に迫ってきたC++17、欲しかった機能が規格に盛り込まれたかどうかは分かりませんが、コンパイラーの実装も結構進んできて、"-std=c++1z"オプションを使えば試せるものも増えてきました。

この記事ではそんな新機能のうち、コア言語機能で、gcc/clangどちらかのコンパイラーに実装されているものを試していこうと思います。
どれが実装されているかとか自分で調べるのは面倒なので、こちらを参照したいと思います。各コンパイラーの対応状況がひと目で分かって便利ですね。

動作確認にはwandboxの"gcc HEAD"もしくは"clang HEAD"を利用します。この記事を書いている時点では

  • gcc HEAD 7.0.0 20161216 (experimental)
  • clang HEAD 4.0.0 (trunk 289950)

となっていますが、おそらく数日で変わるので、後々までサンプルコードが動作する保証はできません。
というか、既に昨日今日あたりでgccが変わってるよ!

そんなわけで、しばらくの間、お付き合いください

N3922 New auto rules for direct-list-initialization

動作確認コード

C++14までの規格では、auto変数のリスト初期化時の挙動は、std::initializer_listになるというものでした。
例えば、

auto a{0}; // std::initializer_list<int>
auto b{'a', 'b', 'c'}; // std::initializer_list<char>

などです。
しかし、実際にautoで型推論を行う場合、std::initializer_listに推論してほしい場面はあまりありません。普通は

auto a{0};

と書かれていればint型になってもらいたいのです。

という訳で、挙動が修正されました。1要素のリスト初期化はリストの中身の型に推論されるようになります。また、2要素以上のリスト初期化は不可能になりました。

auto a{0}; // int
auto b{'a', 'b', 'c'}; // ill-formed

std::initializer_listに推論したい場合は、コピー初期化構文を使うと可能です。逆に、

auto a{0};
auto b = {0};

この2つは挙動が異なるので注意しましょう。

なお、gccでもclangでも、"-std=c++1z"を付けなくてもこの挙動になっています。確かめていませんが、MSVCでさえ14.0でそうなっているようです。ある意味規格違反と言えなくもありませんが、C++14の策定に間に合わなかった規格のバグ修正をコンパイラーが先取りしたようなものでしょう、多分。

N3928 static_assert with no message

動作確認コード

メッセージなしのstatic_assertが書けるようになりました。
……他に何を説明すれば良いのでしょう。多分みんな知ってるし、ようやくかよって思ってるよね。以上。

N4051 typename in a template template parameter

動作確認コード

テンプレートテンプレートパラメーターにtypenameキーワードが使えるようになりました。
つまり、テンプレート引数リスト内でtypenameキーワードとclassキーワードが同じ意味になります。
これも以前から利用可能な機能ではあるので、今更と言えば今更ですね

N4086 Removing trigraphs

動作確認コード

トライグラフは死んだんだ。もう二度と戻っては来ないんだ。
良かったですね。

ただし、gccでは"-Wno-trigraphs"のオプションを付けておかないと、トライグラフに該当する文字列が出現した時に警告が出ます。負の遺産です。

N4230 Nested namespace definition

動作確認コード

namespace a { namespace b { namespace c { } } }

と書いていたのが、

namespace a::b::c {}

と書けるようになります。小粒ですが嬉しい機能ですね

N4266 Attributes for namespaces and enumerators

動作確認コード

名前空間と列挙型、列挙子にattributeを付けることができます。
動作確認コードでは、とりあえず[[deprecated]]属性を付けてみました。gccでは属性を付けること自体はできたのですが、なぜか名前空間と列挙型の[[depreceted]]属性を認識してくれなかったのでclangで試しています。

これでいつでも、化石のようなコードの名前空間に[[deprecated]]を付けて回ることができるようになりますね。え? 名前空間に入ってない?

N4267 u8 character literals

動作確認コード

u8プレフィックスが文字リテラルにも適用できるようになります。
ただし、UTF-8にエンコーディングされたUnicode文字は1〜4バイトなので、2バイト以上の文字はchar型で表せません。という訳で実質U+007F以下の文字しか使えません。
エンコーディングが固定されるので、u8プレフィックスのつかない文字リテラルと違って環境依存性はなくなります。

N4268 Allow constant evaluation for all non-type template arguments

動作確認コード

非型テンプレートパラメーターでポインターや参照を使う場合、いろいろと制約があるのですが、その一部が緩和されます。
具体的には、定数式で得た参照やポインターが、リンケージを持った変数の参照やポインターであれば、非型テンプレートパラメーターにできるようになります。
従って、

  • リンケージを持った参照を返すconstexpr関数
  • constexprに構築可能な一時オブジェクトのstaticメンバ変数

などを非型テンプレートパラメーターに使うことができます。
リンケージが必要なので、配列の要素やstaticでないメンバ変数などはこれまでと同様使えません。

constexpr int& f(int& v) { return v; }
int a;
int b[1];
struct C { int i; static int j; } c;
template<int&> class X {};

X<f(a)> x1; //ok
X<f(b[1])> x2; //ill-formed
X<f(c.i)> x3; //ill-formed
X<f(c.j)> x4; //ok

N4295 Fold Expressions

動作確認コード

パラメーターパックをfold式で展開できるようになります。これにより、何らかの関数呼び出しなどを通さないと扱いづらかったパラメーターパックが扱いやすくなりました。

fold式には4種類があります。
以下の擬似コードで、Eは式、opは演算子、Pはパラメーターパックを含んだ式を表すこととします。
一つのfold式中に出てくるopは同じ演算子を表します。
fold式では、括弧は必須です。

擬似コード
(E op ... op P) // binary left fold
(P op ... op E) // binary right fold
(... op P) // unary left fold
(P op ...) // unary right fold

それぞれのfold式は、以下のように展開されます。P1, P2, ..., Pnは、N個のパラメーターパックのそれぞれの要素を含んだ式です。

擬似コード
(((E op P1) op P2) op ...) op Pn
P1 op (P2 op (... op (Pn op E)))
(((P1 op P2) op P3) op ...) op Pn
P1 op (P2 op (... op (Pn-1 op Pn)))

P0001R1 Remove Deprecated Use of the register Keyword

動作確認コード

register 記憶域指定子がついに取り除かれます。最適化はコンパイラーに任せましょう。
ただし、registerは予約語のままなので、識別子等に使うことはできません。

P0002R1 Remove Deprecated operator++(bool)

動作確認コード

bool型に対するoperator ++が取り除かれます。
元々C++98でdeprecatedになっていたものなので、取り除くには十分な時間が経った、ということのようです。

P0003R5 Removing Deprecated Exception Specifications from C++17

動作確認コード

関数の例外指定で何らかの型を指定することができなくなりました。throw()だけは引き続き使用可能ですが、noexceptと同じ意味なのでnoexceptを使用するべきです。

P0012R1 Make exception specifications part of the type system

動作確認コード
例外指定が型システムに組み込まれることになりました。今までの方式での関数ポインタは全て、noexcept(false)されたものと同じ型になります。
互換性のため、noexcept指定された型からnoexcept(false)指定された型へは暗黙的にキャストされますが、逆はキャストされません。

ところで、論文を見ると、 throw(int)みたいな記述が散見できるのですが、P0003R5に従うなら修正が必要そうです。

P0017R1 Aggregate initialization of classes with base classes

動作確認コード

publicな基底クラスを持つ型のアグリゲート初期化ができるようになりました。基底クラスのそれぞれの型が、メンバ変数の先頭にあるのと同じ構文で初期化をできます。

P0018R3 Lambda capture of *this

動作確認コード

従来のラムダ式では、=でキャプチャー指定してもthisをコピーすることしかできませんでした。thisはポインターなので、コピーされてもメンバーまでコピーすることはできません。
コピーキャプチャーしたつもりで、メンバー変数にアクセスすると既に破棄されていたなんてこともあり得ました。
そこで、*thisをキャプチャーすると、明示的にコピーを行うことができるようになりました。

P0028R4 Using attribute namespaces without repetition

動作確認コード

attributeには、ベンダー固有の名前空間を使って、コンパイラー独自の機能を追加できることになっています。コンパイラー独自のattributeは、名前空間名::属性名といった構文を使って、つまり普通の名前空間解決と同じような書き方をします。
例えばgccなら、これから先[[gcc::foo]]と言った属性が独自機能として用意される可能性があります。

属性は複数同時に指定できるのですが、名前空間に入った属性を複数指定する時に、usingを使って名前空間を指定できるようになっています。
例えば、[[using CC: foo, bar]]と指定すると、[[CC::foo, CC::bar]]と指定したのと同じことになります。

今のところどんなコンパイラー固有の属性があるのかよく把握していませんが、[[gcc::fallthrough]]という属性は存在しているようなので、それで確かめてみました。
ただし、[[fallthrough]]属性はベンダー独自機能ではなくC++17の標準規格として採用予定です。

P0035R4 Dynamic memory allocation for over-aligned data

動作確認コード

new演算子は、std::max_align_t以上のアライメントでメモリを確保します。つまりmax_align_tを超えるアライメントを持つ(over-aligned)型の場合、正しいアライメントでメモリ確保が行われるとは限りません。これは、SIMD命令などを使う場合にしばしば問題になります1。この間もこのアライメント問題を踏んだ人を観測しました。

そこで、new演算子でover-alignedな型が確保された時は、std::align_val_tを受け取るoperator newのオーバーロードが用意されることになりました。正しく実装されていれば、アライメントに合わせてメモリを確保してくれるようになります。
もちろん、他のoperator newと同じように、ユーザー定義を行うことも可能です。

P0036R0 Unary fold expressions and empty parameter packs

動作確認コード

先ほども解説したfold式ですが、unary fold expressionの場合、一つ問題があります。

template<typename ...Args>
auto sum(const Args& ...args) {
    return (args + ...);
}

例えば上記の式ですが、sum(1, 2, 3)と呼び出した場合、1 + 2 + 3の結果が帰ります。
sum(42)とした場合は、42が帰ります。
では、sum()を呼び出した場合は?
unary fold expressionは、パラメーターパックが0個の場合、何を返せば良いのでしょうか。

この問題を解決するため、ある種の演算子に対しては、パラメーターパックが空だった場合のデフォルト値を決めよう、という提案です。

今のところ、&&演算子に対してはtrueが、||演算子に対してはfalseが、,演算子に対してはvoid()がデフォルト値になっています。

ところで、例に出したsumのように+演算子を使う場合と、*演算子を使う場合に関しては当初、それぞれ0および1をデフォルト値にしようという提案だったようですが、これらの演算子に関してはデフォルト値は却下されたようです。
つまり、上記のsum関数に関しては、binary fold expressionにするか、特殊化やオーバーロードでうまいことやるしかないようです。

P0061R1 __has_include in preprocessor conditionals

動作確認コード

プリプロセス時条件式に__has_includeというものが追加されました。
効果は、指定した名前のファイルが存在するかどうかを調べるものです。__has_include("filepath")もしくは__has_include(<filepath>)という構文です。ファイルパスの探索方法は#includeディレクティブと同じです。

P0091R3 Template argument deduction for class templates

動作確認コード

テンプレートクラスの初期化式で、クラスのテンプレート引数を推論できるようになります。
何もしなくても推論してくれる場合と、推論のための新しい構文を使って推論できるようにする場合がありますが、これにちゃんと対応してくれれば、標準ライブラリのコンテナをテンプレート引数なしで初期化できるようになるはずです。

現状、コア言語機能としては対応されていますが、ライブラリの方はまだ対応できていない部分も多そうです。

P0127R2 Non-type template parameters with auto type

動作確認コード

非型テンプレート引数の型名にautoが使用できるようになります。ラムダ式のauto引数と同じような感じで推論されます。
今まで型名を別に指定しなければならなかったところで、型名を指定しなくても良くなるのではないかと期待しています。

なお、gccは現状autoを使用したテンプレートクラスの部分特殊化を行うと、インスタンス化の際にinternal compiler errorが発生するようです。この文章を公開したらバグレポを出そうと思います。

to be continued ...

まだ全部見きれてないのですが、ちょっと変更点の多い論文を理解するのに時間がかかっているので、とりあえずここまでとしようと思います。
constexpr ifなど試したいものもまだあるのですが、近いうちに更新します、ということにさせてください。

明日は、ignis_fatuusさんの記事です。