[C++]ラムダキャプチャの記述順序

ラムダ式を雰囲気で書いているので、キャプチャの正しい順序が分かりません。そのため、コンパイラに怒られて直したり怒られなかったからヨシ!をしています。

正しい順序とは一体何なのでしょうか・・・?

正しいキャプチャ順序

C++20現在、キャプチャには3種類の方法があります。明確に書かれているわけではありませんがそこには確かに順序があり、次のようになります。

  1. デフォルトキャプチャ
    • &
    • =
  2. それ以外
    • 簡易キャプチャ
      • &x
      • x
      • this
      • *this
    • 初期化キャプチャ

はい、これだけです。悩む事も無いですね・・・

// 正しい順番
[&]{};
[=]{};
[=, x]{};
[=, &x]{};
[=, x = 0]{};
[=, this]{};
[=, *this]{};
[=, x, &y, z = 0, this]{};
[=, this, &x, y, z = 0]{};

// 間違った順番
[x, =]{};
[x, &]{};
[&x, &]{};
[&x, =]{};
[x = 0, =]{};
[this, &]{};
[*this, =]{};

= &によるデフォルトキャプチャが先頭にきてさえいれば、後はどういう順番でも構わないという事です。

詳細

ラムダ式の文法定義の中で、ラムダ導入子([])の中のキャプチャ(lambda-capture)は次のように構文定義されています。

lambda-capture:
  capture-default
  capture-list
  capture-default , capture-list

capture-default:
  &
  =

capture-list:
  capture
  capture-list , capture

capture:
  simple-capture
  init-capture

simple-capture:
  identifier ...(opt)
  & identifier ...(opt)
  this
  * this

init-capture:
  ...(opt) identifier initializer
  & ...(opt) identifier initializer

まず最初のlambda-captureを見てみると、capture-defaultcapture-listそれぞれ単体あるいはcapture-default , capture-listの形の列のいずれかとして定義されています。capture-defaultはその次で定義されており、= &のどちらかです。そして、capture-defaultはここ以外では出現しません。

従ってまず、capture-defaultcapture-listよりも前に来なければならない事が分かります。

ではcapture-listとは何なのかと見に行けば、captureあるいはcapture-list , captureのどちらかとして定義されています。この書き方はEBNFにおいて繰り返しを表現する定番の書き方であり、capture-listとは1つ以上のcaptureの列として定義されています。

captureはさらにsimple-captureinit-captureのどちらかとして定義され、ここには順序がありません。

simple-captureは4つのキャプチャが定義されており、上からコピーキャプチャ、参照キャプチャ、thisのコピーキャプチャ、*thisのコピーキャプチャ、が定義されています。ここにもその出現順を制約するものはありません。

init-captureはその名の通り初期化キャプチャを定義しており、コピーキャプチャと参照キャプチャの2種類が定義されています。そしてここにも順序付けはありません。

結局、lambda-captureの中で出現順が定義されているのはcapture-default , capture-listという形式だけであり、これがデフォルトキャプチャ(= &)が先頭に来て後は順不同という事を意味しています。

なお、...(opt)はパラメータパック展開のことで、これはC++20で許可されたものです。これも= &が先頭にきてさえいればどういう順番で現れても構いません。

参考文献

この記事のMarkdownソース