C++14 に追加された機能(の一部)に対する私的レビュー コア言語編

C++14に入ることが決まったものの一覧(みたいなもの) を読んで気になった C++14 での変更のうち,コア言語の中から気になったものを挙げてみる.


ただし,

  • 動的配列
  • 多相ラムダ
  • 変数テンプレート

等に関しては, http://cpplover.blogspot.jp/2013/04/bristolc14.html で説明されているので,省略させて頂いた.

一般関数の型推論

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3664.html


素晴らしい変更.
これによりラムダ式における型推論が一般の関数に対しても可能になる.


つまり,今まで

template<class F, class... Args>
auto apply( F f, Args&&... args )
  -> decltype( f( std::forward<Args>(args)... ) )
{
  return f( std::forward<Args>(args)... );
}

decltype を使って書いていたのが,

template<class F, class... Args>
auto apply( F f, Args&&... args ) {
  return f( std::forward<Args>(args)... );
}

と書けばそれで良くなる,ということだ.*1


また,それと同時に,ラムダ式も含め,型推論できる関数の制約が大幅に緩和されている.
C++11 では

auto f = [](int x, int y) { return x < y ? y : x; };

のように,単一の return 文のみを含む関数でなければ型推論が行えなかったのに対し,
C++14 では

auto f = [](int x, int y) {
  if( x < y ) {
    return y;
  } else {
    return x;
  }
}

のように書いた場合でも推論してくれる.


それどころか,

auto hoge = []{
  struct Internal {
    // なんか実装する
  };
  return Internal{};
}();

のように,ローカルクラスを返すラムダが作れるようになる.



ラムダに対する move capture (Generalized Lambda-capture)

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3648.html


ねんがんの move capture を てにいれたぞ!


これにより,

auto p = std::make_unique<Hoge>();  // make_unique も C++14 で導入される
auto f =
  [p = std::move(p)] {
    return p->do_something();
  };

のように, move しか出来ないクラスをラムダにキャプチャさせることが可能になる.


更に嬉しいことに,この機能は move capture 以外でも使える.

int x = 1;
auto f =
  [x=x+1] (int y) {
    return x + y;
  };

キャプチャされた変数の名前として, x という「キャプチャ元と同じ名前」を使える点にも注目してほしい.


また,参照キャプチャもできる.

std::array<int, 5> a;
auto f =
  [&r = a[1]] (int x) {
    r = x + 1;
  };


非常に便利.

constexpr 大幅強化

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3652.html

凄い. とにかく凄い.
if が使えるようになったぜヒャッハー,とか,そういうレベルじゃない.

constexpr int next(int x) {
  return ++x;  // 副作用!
}

のような関数も問題なくコンパイル時に計算できる. すごすぎる.

new におけるメモリ確保を省略可能に

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3664.html

これも凄い変更. いずれ来るとは思っていたが,まさか C++14 で来るとは.


この変更により,コンパイラは new の際のメモリ確保を省略するような最適化を行なってよいことになる.

つまり,ただでさえ高速な std::string や std::vector が,更に高速になる可能性がある*2,ということだ.

std::string や std::vector に関しては現行規格でもメモリ確保は省略できるようになっている*3のだが,
この変更により,普通の new に対しても その手の最適化が出来るようになるし,
それが切っ掛けとなり std::string や std::vector といったクラスの最適化が行われるようになれば,正に御の字である.


また,この方針を突き詰めていくと,いずれは constexpr new にも到達するであろうことも考えると,
単純な変更のようでいて,実際には非常に夢の広がる変更であるといえるだろう.

range-based for の ADL ルールの変更

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3539.html#1442


range-based for で ADL 経由で begin/end を呼ぶ場合の名前検索ルールの変更.
ADL は邪悪だが,時には必要になる. かもしれない.

具体的には, C++11 の range-based for では, ADL の際に std 名前空間も考慮していたのが,
今回の変更で, std 名前空間は,元から std 名前空間を考慮するような場合を除き,考慮に入れなくなった.


更に, begin/end を ADL で「のみ」検索する(現在のスコープは無視する)旨が,規格に明記された.*4


とはいえ, ADL は邪悪なので,基本的にはメンバ関数 begin/end を定義して使うようにするほうがいいだろう.

具体的な変更としては,

  • std からは begin/end を検索しない
  • 現在のスコープからは begin/end を検索しない

となる.


後者の変更は若干厄介で,

using ns::begin;
using ns::end;

for( auto x : xs ) {
  // 処理
}

というコードがあったとき,この変更が原因でコードが動かなくなる可能性が出てくる.


ともあれ,普通は ADL という邪悪な方法ではなく,メンバ関数の begin/end を使うべきであるので,
この変更が実際のコードに与える影響は少ないだろう.

関数の noexcept 修飾の内部で使われている式の実行を遅延するように

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3539.html#1330


これにより noexcept 節中で SFINAE を行なうことが明確に不可能になる.

void g(std::string s);

// #1
template<class T>
void f(T x) noexcept(noexcept(g(x)));

// #2
void f(...);

int main() {
  f(0); // calls #1 in C++14
}

とはいえ, noexcept 中での SFINAE は最初からグレーゾーンだったので,
C++11 であっても使うべきではないのは確かだ.

deprecated の定義の変更

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3539.html#223


removal という言葉が使われることで,より分かりやすくなった.
良い変更だと思う.

SFINAE ルールの厳密化

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3539.html#1462


"ill-formed; no diagnostic is required" とか書かれていた場合には SFINAE は発動しないことが明記.
C++によほど精通した人でない限りは SFINAE は使うべきではないと思う.

ラムダの引数にデフォルト値を与えられるように

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3539.html#974


これによりラムダ式の擬似多重定義が出来るようになる.
ラムダ式にそこまで求めるのは個人的にはどうかと思うが.

暗黙のうちに削除された move ctor が copy ctor を覆い隠してしまう問題への対応

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3667.html


いわゆる一つのバグ対応.
細かい変更なので C++ マニアでもなければ気にする必要はない.

追記

この記事で説明しなかった C++14 の機能に関しては,
http://isocpp.org/blog/2013/05/post-bristol-standards-papers-mailing-available
で Adopted となったものを確認すれば把握できる.


個々の paper の解説は
http://cpplover.blogspot.jp/2013/05/2013-05-post-bristol-mailing.html
を読むとよい.

*1:ただし,後者の場合には SFINAE は行われない,戻り値は自動で参照が消える,といった差はあるので,前者と後者は厳密には等しくない.

*2:ただし現行の規格のままでは無理 https://twitter.com/k_satoda/status/332533786308902914

*3:https://twitter.com/k_satoda/status/332535840347676672

*4:C++11 でも現在のスコープは無視するよう規定されていたが, C++14 ではその旨が明確に記される.