GCC-4.8.1 がリリースされた

GCC の最新版, GCC-4.8.1 がリリースされました.


GCC-4.8.1 は, GCC-4.8.0 のバグフィックス版であり,多くのバグが潰されている他,
新たに rvalue references for *this が実装されており,これにより
C++11 の主要な言語機能は一通り実装されたことになります.


近いうちに Clang でも C++11 の主要な言語機能を全て実装した版が出ることもあり,
これを機会に C++11 の機能を解禁してみると良いんじゃないでしょうか.


関連: http://d.hatena.ne.jp/gintenlabo/20130518/1368887997

C++14 のラムダ式 完全解説 中編

この記事では,前編に引き続き, C++14 のラムダ式について説明していく.
前編では,ラムダ式に対する大雑把な説明と,ラムダ式の持つ型推論機能を紹介した.
この記事では,ラムダ式の最も重要な機能の一つである,変数のキャプチャについて説明したい.
なお,初めて この記事を読む方は,先に前編を読むことをお勧する.


目次(再掲):

  1. ラムダ式の基礎 (前編で解説)
  2. ラムダ式型推論前編で解説)
  3. 変数のキャプチャ(この記事で解説)
  4. ラムダ式の活用法(執筆中)
  5. マニア向けの補足(執筆中)

変数のキャプチャ

ラムダ式により生成されるクラスの operator() の関数本体は, this ポインタやメンバ変数・関数の扱いを除き,ラムダ式の本体と同じコードになる.

// 例えば,
auto f = [] (int x, int y) { return x + y; }
// 上のコードは,
struct f_ {
  auto operator()(int x, int y) const {
    return x + y;
  }
};
// と同じになる.
// this ポインタの扱いに関しては,あとで説明する.


つまり,ラムダ式の本体の記述には,基本的には関数内クラスで使えるものと同じ物,
例えばグローバル変数や static 変数, constexpr なローカル変数などは自由に使うことができる.

// グローバル変数
std::string str = "hoge";

int main()
{
  // ローカルに定義された定数
  constexpr int n = 10;
  // グローバル変数やローカルで定義された定数は,ラムダ式中で使用できる
  auto f = [] {
    for ( int i = 0; i < n; ++i ) {
      std::cout << str << std::endl;
    }
  };
  f();  // ok
}


一方で,関数内クラスでローカル変数が使えないのと同じように,
ラムダ式の内部では, constexpr でないローカル変数(や関数の引数)は そのままでは 使えない.

void f(int x)
{
  auto g = [] (int y) { return x + y; };  // NG; 引数 x は そのままでは使えない
  auto h = [] { return g(1) * 2; };  // NG; ローカル変数 g は そのままでは使えない
}

それでは不便なので,ラムダ式にはローカル変数のキャプチャという機能が用意されている.


ラムダ式中でのローカル変数へのアクセスは, [] の部分を [&] にすることで可能になる.

void f(int x)
{
  auto g = [&] (int y) { return x + y; };  // OK
  auto h = [&] { return g(1) * 2; };  // OK
  std::cout << h() << std::endl;  // (x + 1) * 2
}

この機能を,ローカル変数の 参照キャプチャ と呼ぶ.
参照キャプチャを使った場合,ラムダ式中で使われている変数は,ラムダ式の外の変数と全く同じものになる.
つまり,ラムダ式の外で変数を変更すると,ラムダ式の中の変数も変更される.

void f(int x)
{
  auto g = [&] (int y) { return x + y; };  // OK
  auto h = [&] { return g(1) * 2; };  // OK
  std::cout << h() << std::endl;  // (x + 1) * 2
  x = 1;  // x を変更すると h() の結果も変わる
  std::cout << h() << std::endl;  // 元々の x の値に関わらず (1+1) * 2 = 4 になる
}

逆に,ラムダ式の中で変数を変更すると,ラムダ式の外の変数も変更される.

void f()
{
  int x = 0;
  auto g = [&] (int y) { x += y; };  // OK, ラムダ式中で参照キャプチャされた変数を変更する
  g(3);
  std::cout << x << std::endl;  // 3
  g(10);
  std::cout << x << std::endl;  // 13
}

これは, C++ 以外の多くの言語におけるラムダ式(もしくは関数内関数)の挙動に近い.

-- 例として Lua を挙げる
function f(x)
  local function g(y)
    return x + y
  end
  x = 10
  print( g(3) )  -- x の元の値に関わらず 13 になる
end

そのため,他の言語のラムダ式を知っているなら,すんなり理解できるだろう.


このように便利な参照キャプチャであるが,何故 この挙動がデフォルトとなっていないかと言うと,
C++ には言語組み込みの GC が無いため,状況によっては寿命問題が起きる可能性があるからだ.

// ラムダ式を返す関数(注: 安全ではない)
auto f(int x) {
  return [&] (int y) {
    return x + y;
  };
}

int main()
{
  auto g = f(1);
  std::cout << g(2) << std::endl;  // undefined behavior; g 内部で使われている f の引数 x は寿命が切れている
}

この問題は,関数(ラムダ式を含む)の戻り値として使った場合の他,
std::function 等の type erasure と組み合わせた時などに発生しうる.*1

int main()
{
  using function_t = std::function<int(int)>;  // typedef よりこっちの方が好き
  std::vector<function_t> vec;
  {
    int x = 10;
    vec.push_back(
      [&] (int y) {
        return x + y;
      }
    );
  }
  for ( auto& f : vec ) {
    std::cout << f(2) << std::endl;  // undefined behavior; f で使われている x は寿命が切れている
  }
}

気をつけるべきは「関数の戻り値」や「再代入をはじめとするオブジェクトへの状態変更*2」で,
これらが関わる場合には,参照キャプチャは危険であり,使うことはできない.


そのような場合に用いるのが,変数の コピーキャプチャ である.
これは,ローカル変数をコピーした値をラムダ式内部で保持する機能であり(そのため「値キャプチャ」とも呼ばれる),
独立した変数となるため寿命問題が起きにくい分,参照キャプチャより安全である.*3


使い方は,

auto f(int x, int y) {
  return [x, y] (int z) {  // x と y を「キャプチャ」する
    return x + y + z;
  };
}

int main()
{
  auto g = f(1, 2);
  std::cout << g(3) << std::endl;  // OK; 1 + 2 + 3 = 6 を表示する

  using function_t = std::function<int(int)>;  // typedef よりこっちの方が好き
  std::vector<function_t> vec;
  {
    int x = 10;
    vec.push_back(
      [x] (int y) {  // x を「キャプチャ」する
        return x + y;
      }
    );
  }
  for ( auto& h : vec ) {
    std::cout << h(2) << std::endl;  // OK
  }
}

のように,使いたい変数の名前を [] の中にカンマ区切りで書けばよい.


ただし,ラムダ式中で使いたい変数がメンバ変数の場合には,そのままでは上手くいかない.

struct Hoge
{
  int x;
  
  auto foo() const {
    return [x] (int y) { return x + y; };  // NG
  }
};

この場合, x をローカルで定義しなおすか,後述の 汎用ラムダキャプチャ構文 を使う必要がある.

struct Hoge
{
  int x;
  
  auto foo() const {
    auto& x = this->x;
    return [x] (int y) { return x + y; };  // OK
  }

  // もしくは
  /*
  auto foo() const {
    return [x = x] (int y) { return x + y; };  // OK; 後で説明する
  }
  */
};

いずれにせよ,コンパイルエラーになったら対処する,で問題ない.


また, [=] と書くことで,変数名のリストを省略することもできる.

auto f(int x, int y) {
  return [=] (int z) {  // = を使うことで,コピーキャプチャする変数を省略できる
    return x + y + z;
  };
}

int main()
{
  auto g = f(1, 2);
  std::cout << g(3) << std::endl;  // OK
}

が,コピーキャプチャは後述の理由で効率が良くない上に,
[=] を使った場合はメンバ変数が暗黙で参照キャプチャされてしまう((厳密に言えば this を値キャプチャしてるのだが,実質的には参照キャプチャである. *this が破棄された時点で dangling reference となる.))ため,
筆者は具体的に名前を羅列することを 強く推奨している.


コピーキャプチャによってキャプチャされた変数は,ラムダ式によって生成されたクラスのメンバ変数となる.
つまり,

auto f(int x, int y) {
  return [x, y] (int z) {
    return x + y + z;
  };
}

これは,

auto f(int x, int y) {
  struct Internal {
    int x; int y;
    auto operator()(int z) const {
      return x + y + z;  // ここで参照される x や y はクラスのメンバ変数
    }
  };
  return Internal{x, y};
}

これと(細かい挙動の差はあるものの)ほとんど同じになる.


メンバ変数は元の変数とは独立しているので,元の変数を変更しても,キャプチャされた値は変化しない.

int main()
{
  int x = 0;
  auto f = [x] (int y) { return x + y; };
  x = 23;
  std::cout << f(10) << std::endl;  // 10 と出力される. 33 にはならない
}

ただし,ポインタ等を使った場合には,(当たり前だが)この限りではない.

int main()
{
  int x = 0;
  int* p = &x;
  auto f = [p] (int y) { return *p + y; };
  x = 23;
  std::cout << f(10) << std::endl;  // 33
}

また,ラムダ式の生成するクラスの operator() はデフォルトで const 修飾されるため,
キャプチャされた値をラムダ式の内部で変更することは,デフォルトでは不可能である.

int sum = 0;
auto acc = [sum] (int x) {
  sum += x;  // NG
  return sum;
};

どうしても変更したい場合は,() の後に mutable キーワードを付与する.

int sum = 0;
auto acc = [sum] (int x) mutable {
  sum += x;  // OK
  return sum;
};

とはいえ,上記のような例は スマートポインタを使うなり単に参照キャプチャするなりすれば良い話であり,
標準アルゴリズムと組み合わせた時など,意外と間違いが起きやすいため,
実際のコードで mutable 修飾を使うケースは あまり存在しないし,使わないようにした方が良いだろう.


さて,コピーキャプチャされた変数はメンバ変数となるので,大量のオブジェクトをラムダ式にコピーキャプチャした場合,
または大きなサイズのオブジェクトをコピーキャプチャした場合には,生成されるラムダ式のサイズは大きくなる.

auto f() {
  std::array<int, 100000000> a;
  for( int i = 0; i < a.size(); ++i ) {
    a[i] = std::rand();
  }
  return [a] (int i) { return a[i]; };
}
int main()
{
  auto g = f();
  std::cout << sizeof(g) << std::endl;  // 100000000 * sizeof(int) くらい
}

上記のようなケースは, C++11 では, std::shared_ptr を使うことで解決できる.

auto f() {
  using array_t = std::array<int, 100000000>;
  auto p = std::make_shared<array_t>();
  auto& a = *p;
  for( int i = 0; i < a.size(); ++i ) {
    a[i] = std::rand();
  }
  return [p] (int i) { return (*p)[i]; };
}
int main()
{
  auto g = f();
  std::cout << sizeof(g) << std::endl;  // sizeof(std::shared_ptr<int>) と同じくらい
}

変数の数が多い場合には,関数内クラスか std::tuple と組み合わせた上で std::shared_ptr を使うと良い.


上記のコードは std::array ではなく std::vector を使えばよいと思うかもしれないが,
その場合はコピーキャプチャの際に変数のコピーが発生するため,思うほど速くはならない.((ただし,スタックを食いつぶさないようにする,という点においては, std::vector の方が良い.))
また, std::shared_ptr ではなく std::unique_ptr を使えばよいと思うかもしれないが,
ラムダ式のコピーキャプチャはコピーコンストラクタを要求するため,やはり上手くいかない.


そこで登場するのが, C++14 で追加された 汎用ラムダキャプチャ構文 である.

auto f() {
  std::vector<int> vec;
  for( int i = 0; i < 100000000; ++i ) {
    vec.push_back( std::rand() );
  }
  return
    [vec = std::move(vec)] (int i) {
      return vec[i];
    };
}
int main()
{
  auto g = f();
  std::cout << sizeof(g) << std::endl;  // sizeof(vector<int>) くらい
}

上記のように, [] の中に 変数名 = 式 のリストをカンマ区切りで書くと,

auto f() {
  std::vector<int> vec;
  for( int i = 0; i < 100000000; ++i ) {
    vec.push_back( std::rand() );
  }
  struct Internal {
    std::vector<int> vec;
    auto operator()(int i) const {
      return vec[i];
    }
  };
  return Internal{ std::move(vec) };
}
int main()
{
  auto g = f();
  std::cout << sizeof(g) << std::endl;  // sizeof(vector<int>)
}

のような感じの処理になる. 変数の型は auto で推論されるものと同じになる.
なお,上記のコードで分かるように,ラムダ式の中で使う変数名は,ラムダ式の外の変数名と同じで良い. ((auto x = std::move(x); とは書けない(厳密に言うなら,書けるけど意図した動作にはならない)のと対照的である.))


これを使えば, std::unique_ptr のような コピーコンストラクタのないクラスも
ラムダ式の内部で使うことができるようになる他,
ローカル変数に対し,手を加えた上でラムダ式にキャプチャさせることもできる.

auto f(int x) {
  return [y = x * 2] (int z) {  // x = x * 2 とも書けるが,紛らわしいので回避したほうが吉
    return y + z;
  };
}

変数名のみを書いたコピーキャプチャと混ぜて使うこともできる.

auto f(int x) {
  return [x, y = x * 2] (int z) {
    return x + y + z;
  };
}

&変数名 = 式 という形で,参照キャプチャを行うこともできる. もちろん寿命には気をつける.

auto f() {
  auto p = std::make_shared<Hoge>();
  return [p, &x = *p] {
    // 処理をここに書く
  };
}

&x = x のような形は,単に &x と書くこともできる.*4

auto f() {
  // std::unique_ptr を使って効率化
  auto p = std::make_unique<Hoge>();
  auto& x = *p;
  return [&x, p = std::move(p)] {  // 寿命問題のため [&x = *p, p = std::move(p)] とは書けない 
    // 処理をここに書く
  };
}

なお,ラムダ式のキャプチャは,書いた順番通りに行われるとは限らないため,
上記のコードで [&x = *p, p = std::move(p)] と書いてしまうと, *p の参照外しに失敗する可能性がある.
それ以外にも,参照は色々と落とし穴があるので,よく分からない方は使わないのが良いだろう.


* * * * *


と,ラムダ式のキャプチャについて,基本的な部分は解説できたことと思う.
これ以外にも, this のキャプチャなど,細かいルールは色々とあるのだが,普通に使う分には関係ないので,
そういう細かいルールは明日,最後の章でまとめて紹介してきたい.


蛇足までに,参照キャプチャとコピーキャプチャが分かりにくいという方のため,簡単なコードを用意してみた.

int main() {
  // ラムダ式の外でローカル変数を変更
  int x = 1, y = 2, z = 3;
  auto f = [&] { return x + y + z; };  // 参照キャプチャ
  int* p = &z;
  auto g = [x, y, z] { return x + y + *p; };  // 値キャプチャ
  // auto g = [x, y, p = &z] {return x + y + *p; }; // こう書くこともできる
  f();  // 6
  g();  // 6
  x = 10;
  f();  // 15; x は変更されている
  g();  // 6; g 内部の x は変更されていない
  x = 1;
  z = 10;
  f();  // 13
  g();  // 13; g 内部の p は z を指している
  p = &x;
  f();  // 13
  g();  // 13; g 内部に保持されている p はコピーなので, z を指したまま

  // ラムダ式内部からローカル変数を変更
  {
    int sum = 0;
    auto acc = [&] (int i) { sum += i; };
    for (int i = 1; i <= 10; ++i) {
      acc(i);
    }
    std::cout << sum << std::endl;  // 55
  }
  {
    int sum = 0;
    auto acc = [sum] (int i) mutable { sum += i; return sum; };
    // もしくは
    // auto acc = [sum = 0] (int i) mutable { sum += i; return sum; };
    for (int i = 1; i <= 10; ++i) {
      acc(i);
    }
    std::cout << sum << std::endl;  // 0
    std::cout << acc(0) << std::endl;  // 55
  }
}

イメージを掴む助けになれば良いのだが.


* * * * *


今回のまとめ:

  • ラムダ式の中ではローカル変数以外は自由に使える. ローカル変数を使いたい場合は,キャプチャをする必要がある
  • [&] は参照キャプチャ. ラムダ式の中からローカル変数を操作できるが,寿命に注意
  • 変数名を [] の中に書くとコピーキャプチャ. 寿命問題を解決できるが効率が悪くなる場合がある
  • コピーキャプチャは std::shared_ptr と組み合わせると便利である
  • C++14 では [x = std::move(x)] のように書くことができる. この場合 x は move される(ムーブキャプチャ)
  • 上記の構文は move 以外でも使うことができ,ローカル変数に対して手を加えた上でキャプチャすることが可能


前回と今回で,ラムダ式の機能はだいたい解説することができた.
次回(最終回)では,これらの機能をどのように使えば効果的なのか,という例を紹介し,
おまけとして,完全解説と銘打つ以上は必要と思われる,マニア向けの細かいルールも紹介したい.
私生活が慌ただしくなってきたため,また時間が空いてしまうかもしれないが,最後まで お読み頂ければ幸いである.

*1:過去の記事 http://d.hatena.ne.jp/gintenlabo/20110121/1295635329 も参考のこと.

*2:グローバル変数の状態変更は,他の関数の内部で行われていた場合,コードから見えにくいので,特に注意が必要である. なお,そういうコードはスレッドセーフでないケースが多いので,参照キャプチャが関わらなくても気をつけた方が良い.

*3:ポインタ変数などを使う場合は その限りではないが,それは C++ の他の機能と同じである.

*4:厳密には その両者は異なるのだが,大雑把に考えるならそう解釈してよい.

GCC-4.8.1 の Release Candidate が公開された

GCC の最新版, GCC-4.8.1 の Release Candidate が公開されました.

ダウンロードは ftp://gcc.gnu.org/pub/gcc/snapshots/4.8.1-RC-20130517 から行えます.


GCC-4.8.1 は, C++11 のコア言語機能が一通り実装された最初のバージョンになります.*1

特に問題が起きなければ, GCC-4.8.1 の正式リリースは 来週の後半(5月末か6月頭 24日前後?)になるとのことです.

追記 (5/28)

公開されていた Release Candidate に重大なバグが幾つか報告されたため,
正式リリースは延期され,新たな Release Candidate が公開されました.

ダウンロードは ftp://gcc.gnu.org/pub/gcc/snapshots/4.8.1-RC-20130527 から行えます.
GCC-4.8.1 のリリースは,今週の終わり頃*2になる予定だということです.

*1:なお,現在開発中の GCC-4.9 では,既に C++14 の機能の一部が実装されています.

*2:筆者注: 6月頭くらい?

C++14 のラムダ式 完全解説 前編

C++14 の Committee Draft が公開された


C++14 は基本的には C++11 のマイナーバージョンアップであるが,バグフィックスのみを行っている訳ではなく,
C++11 の時点で微妙に使いにくかった機能,特にラムダ式については,大きな機能追加が行われている.


そこで,本 blog では,このエントリから数回に分けて, C++14 のラムダ式について説明してみることにする.
拙い文章になるかとは思うが,読者の理解の助けになれば幸いである.


なお,これらの記事を書くにあたって,読者に対して C++11 のラムダ式に対する知識を要求しないように心がけたが,
もしかしたら,説明不十分であり,分かりにくい部分があるかもしれない.
そのような場合には, 本の虫: lambda 完全解説 等, C++11 のラムダについて書かれた記事は多いので,
それらの記事を読んでみることを お勧めしたい.


また,以降のエントリは N3690 に基づいて書かれたものであり,今後の展開次第では大きな変更が来る可能性があること,
また筆者の規格の解釈が間違っている可能性も十分にあることは,予め言及させていただく.
もしツッコミどころがありましたら,気軽にコメントなり Twitter なりで お教え下さい.


目次:

  1. ラムダ式の基礎 (この記事で解説)
  2. ラムダ式型推論(この記事で解説)
  3. 変数のキャプチャ(中編で解説)
  4. ラムダ式の活用法(後編で解説予定)
  5. マニア向けの補足(後編で解説予定)

ラムダ式の基礎

ラムダ式とは,文字通り「式」である.


式ということは,つまり値を持つ.*1
値のない式も C++ には例外的に存在するが,ラムダ式はそのような例外ではない.
きちんと値をもった,立派な式である.
では,ラムダ式の値とは, 一体 何なのか.


ラムダ式の値は関数オブジェクトである. 名前は特にない.
C++ における関数オブジェクトとは,

int main() {
  struct plus_one_t {
    auto operator()(int x) const -> int {
      return x + 1;
    }
  };
  plus_one_t plus_one = {};
  std::cout << plus_one(0) << std::endl; // 1
}

のように, operator() が定義された,まるで関数のように使えるオブジェクトのことだった.


同じようなコードをラムダ式を使って書くと,

int main() {
  auto plus_one = [] (int x) -> int {
    return x + 1;
  };
  std::cout << plus_one(0) << std::endl; // 1
}

となる.


先ほどのコードと比較すると,クラスを予め宣言しておく必要がない,
地味だが const 指定を明示的に書かなくていい,といった点で,
かなりコードがシンプルになっていることが分かるだろう.


ラムダ式は,(この後に説明するキャプチャを使わない場合には,)要するにこれだけの機能だ.
とはいっても,慣れない人には少々奇妙に思えるかもしれないので,もう少し練習してみよう.

// 複数引数も普通に使える
auto add = [] (int x, int y) -> int { return x + y; };
add(1, 2); // 3
// 無引数でも問題ない
auto dice = [] () -> int { return std::rand() % 6 + 1; };  // 簡便のため std::rand を使う(本当は良くない)
dice();  // 1 〜 6 のどれか
// void を返す関数
auto print = [] (int x) -> void { std::cout << x << std::endl; };
print(1);  // 1 を出力

こんな感じである. まだ奇妙に感じるかもしれないが,慣れれば特に違和感は感じなくなる(と思う).
ラムダ式というものがどういうものか,これで少し分かったのではないだろうか.

-- ちょいと脱線 --
なお,上の例では,ラムダ式の結果を変数に代入して使っていたが,
ラムダ式は普通の式であるので,もちろん変数に代入しなくても使うことが出来る.
最もポピュラーなのは,関数の引数として使うことだろう.

// <algorithm> の関数と組み合わせる
std::vector<int> v = {1, 2, 3, 4, 5};
std::for_each( v.begin(), v.end(),
  [] (int x) -> void { std::cout << x << std::endl; }
);

勿論,それ以外にも使用方法は沢山ある.
そのうちの幾つかは,この記事でも追って解説するので,楽しみにしておいてほしい.
-- 脱線ここまで --

ラムダ式型推論

さて,先ほどのラムダ式では,ラムダ式の戻り値の型を明示的に書いていたが,
実のところ,ラムダ式の戻り値の型は,明示的に書かなくても,コンパイラによって推論される:

auto plus_one = [] (int x) {
  return x + 1;  // ここから戻り値の型は int と分かるので -> int は書かなくていい
};
auto print = [] (int x) {
  std::cout << x << std::endl;  // return 文が無い場合は void と推論される
};

推論に使われるのは return 文であり,たとえばラムダ式内部に return 0; と書かれていた場合には,
0 の型は int であるため,そのラムダ式の戻り値の型は int と推論される.((なお, return {1, 2, 3}; という形で書かれていた場合は,型推論は失敗する.))
ラムダ式内部に 何らかの値を返す return 文が存在しない場合には,ラムダ式の戻り値の型は void と推論される.


ラムダ式が引数を取らず,その戻り値型が自動で推論される場合,
つまりラムダ式[] () { 〜; } のような形で表される場合には,
[]{ の間に現れる () は,省略することができる.

// 戻り値がある例
auto f = [] { return 42; };

// 戻り値が void の例
auto g = [] {
  for (int i = 0; i < 10; ++i ) {
    std::cout << i << std::endl;
  }
};

上記の fg は,いずれも引数を持たない関数となる.


さて,C++11 時点のラムダ式では,戻り値が void の場合を除き,
ラムダ式本体が { return 式; } という形になっていない限り,型推論は不可能であった.


それは不便だ,ということで, C++14 のラムダ式では,その制限は撤廃される:

auto f = [] (double x) {
  if (x < 0) {
    return std::numeric_limits<double>::quiet_NaN();
  }
  else {
    return std::sqrt(x);
  }
};

上記のような式は C++11 ではコンパイルできないが, C++14 ではコンパイルできるようになる.

-- ちょいと脱線 --


なお,複数の return がある場合には,
それによって帰される値の型が一致してない場合には,コンパイルエラーとなる:

auto f = [] (double x) {
  if (x < 0) {
    return 0;
  }
  else {
    return std::sqrt(x);
  }
};

上記のコードは, 0int 型である一方, std::sqrt(x)double 型なので,
型推論に失敗してコンパイルエラーとなる.


また,戻り値の型推論によって決定される型は, C++11 でも C++14 でも,参照や const の付かない型になる.

auto deref = [] (int* p) { return *p; };  // 戻り値の型は int であって int& ではない

型推論を使いつつ参照を返したい場合には, C++14 ならば,戻り値の型に auto& を指定すればいい.

auto deref = [] (int* p) -> auto& { return *p; }; // 戻り値の型は int& になる

この他, auto const&auto&& といったものも使える.
これらの型推論のルールは,例えば auto const& ならば

auto const& result = 式;

と書いた時に decltype(result) で得られる型に等しくなる.


-- 脱線ここまで --


それに加え, C++14 では, auto を使うことで,引数に対しても型推論を行えるようになる.

auto plus_one = [] (auto x) {
  return x + 1;
};
plus_one(0);       // ok, int 型を返す
plus_one(0.0);    // ok, double 型を返す
int a[10];
plus_one(&a[0]); // ok, int* 型を返す

これは, operator() がテンプレートとして定義されたクラスを使って

struct plus_one_t {
  template<class T>
  auto operator()(T x) const {
    return x + 1; // C++14 ではラムダ以外の関数でも型推論ができる
  }
};
plus_one_t plus_one = {};

と書いたのと ほぼ同じだ(ただし,関数内部でテンプレートは書けないため,上記のコードは書かれた場所によってはコンパイルエラーになる).


この場合,引数は値渡しになるので,それでは効率が悪いと感じる場合*2には,

auto get_size = [] (auto const& x) {
  return x.size();
};

のように auto const& を用いる. この場合は

struct get_size_t {
  template<class T>
  auto operator()(T const& x) const {
    return x.size();
  }
};
get_size_t get_size = {};

と書いたのと同じだ.
同様に auto&auto&&auto const* なども使える.

-- ちょいと脱線 --
なお, x の型を調べたい場合は, decltype を使えばいい.
特に auto&& を使った場合,これは T&& による型推論と同じなので,
正しく転送するには Perfect Forward を行う必要があるが,これは decltype を使って

auto f = [] (auto&& x) { return g( std::forward<decltype(x)>(x) ); };

と書く必要がある. 複雑なことをしようとした場合には よく出るパターンなので覚えよう.
-- 脱線ここまで --


また,複数の引数を auto で渡すことも出来る.

auto add = [] (auto x, auto y) { return x + y; };

この場合,引数の型に現れた auto 1つにつき1つのテンプレート引数が導入される.

struct add_t {
  template<class T, class U>
  auto operator()(T x, U y) const {
    return x + y;
  }
};
add_t add = {};

この動作は,うっかり忘れることもあるので気をつけよう(もっとも,忘れても特に困らないが).


更に, ... を使うことで,可変個引数にも対応できる.
これは,ほぼ専ら auto&&... という形で Perfect Forward に用いると思って良い.

auto f_ = [] (auto&&... args) { return f( std::forward<decltype(args)>(args)... ); };

この機能は,多重定義された関数をテンプレートに渡したい場合に重宝する.*3

// 上記のように f_ が定義されてるとする

std::vector<int> v = {1, 2, 3, 4, 5};
std::for_each( v.begin(), v.end(), f  ); // f が多重定義されてる場合,コンパイルできない
std::for_each( v.begin(), v.end(), f_ ); // ラムダ式は関数オブジェクトなので問題ない

もちろん,それ以外の場合(例えば先頭に引数を追加したい場合)にも使える.


このように, C++14 のラムダ式は,豊富な型推論機能を持っている.
とはいえ,実のところ, C++14 では,ラムダ式ではない普通の関数も,ほぼ同等の型推論機能を持っていたりする.
が,引数に対する auto 指定はラムダ式でないと書けないし,
ならばテンプレートを使えばいいかというと,関数の内部で定義されたクラスに対しては テンプレートは使えない,
仮に関数内部でのテンプレートが解禁されても,多重定義された関数はそのままでは他の関数の引数として使えない,
といった事情があり,ラムダ式型推論機能は C++14 に不可欠なものとなっているのである.


しかし,実のところ,ラムダ式の凄さは 型推論だけに留まるものではない.
次回の記事では,ラムダ式ラムダ式たらしめている機能,「変数のキャプチャ」について説明したい.

* * * * *


今回のまとめ:

  • ラムダ式とは関数である. ローカル変数に入れたり,標準アルゴリズムの引数として使ったりできる
  • ラムダ式は関数オブジェクトを生成する. ラムダ式の本体は生成されたクラスの operator() になる
  • ラムダ式の戻り値の型を省略することで,コンパイラに推論させることができる
  • ラムダ式の引数の型を auto にすることで,引数の型も推論させられる(多相ラムダ)
  • 多相ラムダは,実装的にはテンプレートになる. テンプレートなので ... も使える
  • ラムダ式を使うことで,多重定義された関数を関数オブジェクトに変換し,関数の引数に渡すことが可能になる

*1:厳密には value category も持つ. 今回は特に そこには言及しないが,参考までに,ラムダ式value category は prvalue である. わからない人はスルーしていいよ.

*2:実際には値渡しのほうが効率が良くなるケースも多いが,流石に std::vector などの場合には値渡しは厳しい

*3:本来は 多重定義された関数も そのままテンプレートで扱えるようにするべきだが,うまく規格を定めるのは難しいだろうし,また今回の記事の本題とはズレるので,その話題については割愛させていただく.

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 ではその旨が明確に記される.

野良C++erの今更な退職エントリ,または私は如何にしてリモートの仕事を辞めて無職になったか

関連エントリ:


Twitter で僕をフォローしてる人は知ってるかもしれませんが,4月から無職に戻りました.
なにぶん突然の事だったので,冷静に記事を書けるか分からず,今まで報告を先延ばしにしてきましたが,
これ以上待ってもたぶん何も状況は改善しないので,思いの丈を書いてしまうことにします.


とりあえず事情を三行で説明すると,

  • 僕に仕事を紹介してくれた人(サーバ側メインの開発者)が体調を悪化してきゅうきょ退職することに
  • 代わりとなる人が見つからず,リリース前で余裕が無い状況だったので, Rails で書いていたコードを PHP で書きなおすことに
  • PHP の実務経験のない僕はアワレにもチーム内で役目を果たせず辞職していた
  • 関係ないけど C++14 いいね

って感じです.


補足するなら,僕を仕事に誘ってくれた人が抜けると,ほぼ間違いなく僕とチームの信頼関係はなくなる,
会社の金を使って東京に滞在し続けるほどの生産性が PHP 使ったことない僕にあるわけもなし,
といろいろ考えると,まぁこれ以上リモートで仕事を続けるのは無理だろう,という感じでした.


僕としては,なにせ突然のことで驚いたわけですが,
お世辞にも扱いやすい人間ではない以上,信頼関係を作れなかった非は僕にもあるし,
何よりこのまま仕事を続けても お互いにとって良くない事態になりそうなのは明白だったので,
じゃあ転職した方がいいかな,リモートは懲りたし地元だと良い仕事ないっぽいし東京辺りで探そうか,と,
割と気楽な感じで「じゃあ辞めます」と了承した次第です.


まぁ,今になって思えば,物凄く早まった選択肢だったわけですけれども.
もう少し冷静に考えてから決断するべきだったなー,と.


一番の誤算は,東京で仕事を探すことに実家の家族が反対したことで,
自分の経験を活かせるような仕事は東京の方が遥かに選択肢が多い,ということを説明しても,
過去の負の実績*1から信頼してもらえず,
実家に多大な借りがある身としては反対を押しきれなかった点でしょうか.


他にも,辞める際に いろいろと不満がなこと*2があって,
胸中は未だに結構複雑なのですが,まぁ,後悔は特にしてないです. 続けてもロクな事にならなかっただろうし.


一つ心残りがあるとすれば,退職の際,チームのメンバーに直接なりメールなりで挨拶することが出来なかった点でしょうか.
どんな胸中であれ,今まで恩を受けたのは間違いない以上,礼を尽くすべきだったのは間違いありません.*3
実際,何処の馬の骨とも分からぬ僕を,かなり良い待遇で雇ってくれただけでなく,
何をやっているかの連絡が未熟ゆえに殆ど行き届いてなかった*4にも関わらず文句の一つも言わずにいてくれた等,
短い間ながらも 受けた恩は少なからずあるし,何より得がたい経験が出来たのは間違いないので,
この場を借りてお礼とお詫びをしておこうと思います.
力及ばず辞めることになり,申し訳ありません. そして,本当に,ありがとうございました.
リリースは まだみたいですが,元メンバーとして,半ばで辞めたことを後悔する程に大成功することを願っています.


さて,僕の今後の展望ですが,
東京に出ることを実家に反対された以上,一先ずは地元である群馬で仕事する感じです.
群馬でソフト関連だと,正直 僕の今までの経験が活きるような職場はなさそうなので,
一先ずはフリーターとしてソフト関連とは関係ない分野で働きながら,趣味のコーディングを進めていこうと考えてます.
いま気になっているのは,やはり C++14 でしょうか. あと趣味プロジェクトを復活させていきたいです.
で,数ヶ月から数年くらい過ごして,家族への借りを返して信頼を回復できたら,
また東京に出て,仕事でのプログラミングをしていければ良いな,と思っています.
その時は求職エントリとか書くかもしれません. 年齢や経歴的に厳しいかもしれませんけどね.

*1:大学を4回留年した挙句に中退したり,初の会社づとめは1年持たなかったり

*2:ここでは特に書きません,詳しく知りたい方は4月分の twilog とか読むといいかと

*3:この辺りの未熟さは本当に痛いほど自覚してて,実際 信頼関係を上手く築けなかった原因の一つだと思います

*4:実際これが信頼を築けなかった最大の原因だと思います. ハッキリ言って自業自得. リモートはもう懲り懲りです

Restricted Implicit Cast

related to: http://d.hatena.ne.jp/gintenlabo/20130416/1366130964


以下のような C++ コードを考える:

// http://ideone.com/s3I4n6
#include <iostream>

int main() {
  auto x = 0;
  double const& ref = x;
  x = 23;
  std::cout << x << std::endl;
  std::cout << ref << std::endl;
}

このコードをコンパイルして実行すると,

23
0

と出力される.


何故なら,一見して x を参照しているように見える ref は,実のところ
int から double へ暗黙変換が行われた結果の一時オブジェクトを参照しているからだ.


このコード http://ideone.com/EWG7Xn を実行してみると分かりやすい:

#include <iostream>
 
int main() {
  auto x = 0;
  double const& ref = x;
  x = 23;
  std::cout << &x << std::endl;
  std::cout << &ref << std::endl;
}

表示された二つのアドレスは,違うものになっているはずだ.


もちろん,普通に C++ を使うならば,このコードは

#include <iostream>
 
int main() {
  auto x = 0;
  auto const& ref = x;
  // ...
}

のように, auto を使って書けば良い.


仮に参照の型をチェックしたいのであれば, この前の記事で定義した implicit_cast と非 const を使って

#include <type_traits>
#include <utility>

template<class To>
To implicit_cast( typename std::enable_if<true, To>::type x ) {
  return std::forward<To>(x);
}

#include <iostream>

int main() {
  auto x = 0;
  auto const& ref = implicit_cast<double&>(x);  // error, int& cannot be converted to double&
  // ...
}

と書くか, std::reference_wrapper を使って

#include <iostream>
#include <functional>

int main() {
  auto x = 0;
  std::reference_wrapper<double const> ref = x;  // error, std::reference_wrapper<T>::reference_wrapper(T && ) is deleted
  // ...
}

と書くか,あるいは static_assert を用いて

#include <iostream>
#include <type_traits>

int main() {
  auto x = 0;
  auto const& ref = x;
  static_assert( std::is_same<typename std::decay<decltype(ref)>::type, double>::value, "" ); // error!
  // ...
}

と書けばよい.


しかし,これらの方法は,いずれも問題がある.


まず単純に, std::reference_wrapperstatic_assert を使う方法は面倒だ.


そして implicit_cast を使う方法は,

int main() {
  auto const x = 0.0;
  auto const& ref = implicit_cast<double&>(x);  // error, double const& cannot be converted to double&
  // ...
}

このように const 修飾に弱い.((このケースだと元々のオブジェクトが const なのでコピーされても問題ないが,非 const オブジェクトに対する const 参照と,元々 const なオブジェクトに対する参照は,言語的には区別できない.))


そこで,変換対象の型として参照型が指定されたとき,一時オブジェクトへの参照を作らない
ように定義された implicit_cast があるといいな,という話になる.


というわけで,作ってみた.
変換先が関数型や void である場合に対処してなかったり, noexceptconstexpr にも対応してないなど
不足はあるが,それでも基本の機能には変わりがない.

#include <type_traits>
#include <utility>
#include <cassert>

// #1
template<class To, class From,
  typename std::enable_if<
    std::is_object<To>::value &&
    std::is_convertible<From, To>::value
  >::type* = nullptr
>
To restricted_implicit_cast(From && x) {
  return std::forward<From>(x);
}

// #2
template<class To,
  typename std::enable_if<
    std::is_lvalue_reference<To>::value ||
    (std::is_object<To>::value && std::is_move_constructible<To>::value)
  >::type* = nullptr
>
To restricted_implicit_cast(typename std::enable_if<true, To>::type && x) {
  return std::forward<To>(x);
}

// #3
template<class To,
  typename std::enable_if<
    std::is_lvalue_reference<To>::value
  >::type* = nullptr
>
To restricted_implicit_cast(typename std::remove_reference<To>::type &&) = delete;

// #4
template<class To, class From,
  typename std::enable_if<
    std::is_rvalue_reference<To>::value &&
    std::is_convertible<From, To>::value
  >::type* = nullptr,
  class = decltype( ::restricted_implicit_cast<To&>(std::declval<From&>()) )
>
To restricted_implicit_cast(From && x) {
  To &  t1 = x;
  To && t2 = std::forward<From>(x);
  assert( std::addressof(t1) == std::addressof(t2) );
  return std::forward<To>(t1);
}

#include <iostream>

int main()
{
  auto x = 0;
  auto const& ref = restricted_implicit_cast<double const&>(x);  // error, conversion to double const& from int& needs an implicit temporary
}

それぞれの定義を,順に解説したい.


まず #1 は,変換先が参照ではない場合の型変換だ.
これは通常の Perfect Forward を使っているので,分かっている人ならば特に問題はないだろう.


次に #2 は lvalue reference 版であり,また restricted_implicit_cast(0) のような変換に対応するものだ.
is_move_constructible で条件チェックしている以外は前回の記事の implicit_cast と同じなので,これも良いだろう.


その次の #3 では, C++11 の std::reference_wrapper の実装を参考に,

int const x = 0;
implicit_cast<double const&>(x); // ok
restricted_implicit_cast<double const&>(x); // error, because temporary is implicitly created

のような例を避けている.
これは, T && 型に対しては, T const& 型より T const&& 型の方が
多重定義解決において優先順位が高いことを利用したものだ.


最後の #4 は,おまけではあるが,変換対象が rvalue reference の場合に対処している.

int const x = 0;
restricted_implicit_cast<double&&>(x); // error, because temporary is implicitly created

その実装は
「変換元も変換先も lvalue にしたうえで restricted_implicit_cast すれば,まぁ一時オブジェクトは作られないだろう」
という楽観に基づいているため,例えば

template<class T>
class Hoge {
  T x;
  operator T const& () const& { return x; }
  operator T&& () && { return std::forward<T>(x); }
};

のようなクラスには対処できない(エラーになるべきではない場所でエラーになる)が,
そういう場合は素直に無印の implicit_cast を使えばいい,ということで.


で,こういうコードを書いてて,
これやっぱ,暗黙変換結果の参照束縛禁止機能を, attribute 辺りを使って言語組み込みでサポートするなり,
せめて restricted_implicit_cast 的な機能を持つ関数を標準ライブラリに用意しておくべきなんじゃね?
って思ったのでした.