ラムダ式を雰囲気で書いているので、キャプチャの正しい順序が分かりません。そのため、コンパイラに怒られて直したり怒られなかったからヨシ!をしています。
正しい順序とは一体何なのでしょうか・・・?
正しいキャプチャ順序
C++20現在、キャプチャには3種類の方法があります。明確に書かれているわけではありませんがそこには確かに順序があり、次のようになります。
- デフォルトキャプチャ
-
& -
=
-
- それ以外
- 簡易キャプチャ
&xxthis*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-defaultかcapture-listそれぞれ単体あるいはcapture-default , capture-listの形の列のいずれかとして定義されています。capture-defaultはその次で定義されており、= &のどちらかです。そして、capture-defaultはここ以外では出現しません。
従ってまず、capture-defaultはcapture-listよりも前に来なければならない事が分かります。
ではcapture-listとは何なのかと見に行けば、captureあるいはcapture-list , captureのどちらかとして定義されています。この書き方はEBNFにおいて繰り返しを表現する定番の書き方であり、capture-listとは1つ以上のcaptureの列として定義されています。
captureはさらにsimple-captureとinit-captureのどちらかとして定義され、ここには順序がありません。
simple-captureは4つのキャプチャが定義されており、上からコピーキャプチャ、参照キャプチャ、thisのコピーキャプチャ、*thisのコピーキャプチャ、が定義されています。ここにもその出現順を制約するものはありません。
init-captureはその名の通り初期化キャプチャを定義しており、コピーキャプチャと参照キャプチャの2種類が定義されています。そしてここにも順序付けはありません。
結局、lambda-captureの中で出現順が定義されているのはcapture-default , capture-listという形式だけであり、これがデフォルトキャプチャ(= &)が先頭に来て後は順不同という事を意味しています。
なお、...(opt)はパラメータパック展開のことで、これはC++20で許可されたものです。これも= &が先頭にきてさえいればどういう順番で現れても構いません。