Conversation
|
"block scope"の定義はラムダ式固有ではなく、 [basic.scope.block]/p1 に従うのではないでしょうか。
[expr.prim.lambda]で定義される構文要素 lambda-expression のうち、 compound-statement つまりラムダ式本体 本件は CWG 2380 capture-default makes too many references odr-usable で後付け修正された内容に関連するようです。 |
|
まず、P0588R1のやっていることは4つあって
だと思います。4はほぼオマケです。 大前提としてラムダ式がキャプチャする必要があるものは常にローカルなものです。ここではそれはローカルエンティティとして指定されており、ほぼローカル変数と ここでのodr-usableとはおそらく、ある名前をラムダ式がキャプチャできるのかを言うために導入されており、キャプチャするのかどうか不明瞭だったところを弾く(あるいはキャプチャ範囲を狭める)ためにodr-usableではない場合は不適格、としています。これはCWG2380によって事後的にも制限されています。 従って、odr-usableという概念は最初のやっていることの1と3に関わるものです。 その上で、odr-usableとはまず、ローカルエンティティに対する概念であって あるローカルエンティティがその宣言領域(シャドウイングされないで名前が有効な領域、スコープ)内で参照される場合、そのエンティティもしくはその場所が
のどちらかに該当しており そのローカルエンティティが導入される地点とそのローカルエンティティが参照される領域との間に介在している宣言領域のそれぞれについて
のどちらかに該当する場合に、そのローカルエンティティはodr-usableとなります。 介在する宣言領域というのは、ローカルエンティティの導入(宣言/定義)地点から、そのローカルエンティティ(の名前)を参照する地点の間に存在している宣言領域(主に各種スコープのこと)です。介在する(intervening)というのは、参照地点から導入地点の間でそのスコープが重なっている様を言っているのだと思います 前段の条件の2は、P0588R1のやっていることの1に関わるもので、クラスメンバ初期化子と非静的メンバ関数の引数宣言で P0588R1の中程で、ラムダ式が明示的にキャプチャするもの(ローカルエンティティ)はodr-usableでなければならないとされています(これも今回関係ありません)。 で、このPRのメインの謎であるサンプルコードが含まれる例を見ていくと void f(int n) {
[] { n = 1; }; // #1 error: n is not odr-usable due to intervening lambda-expression
struct A {
void f() { n = 2; } // #2 error: n is not odr-usable due to intervening function definition scope
};
void g(int = n); // #3 error: n is not odr-usable due to intervening function parameter scope
[=](int k = n) {}; // #4 error: n is not odr-usable due to being
// outside the block scope of the lambda-expression
[&] { [n]{ return n; }; }; // #5 OK
}この例の場合、ローカルエンティティ
多分このサンプルの言いたいことは、関数ローカル変数を関数の外に持ち出すことができうるケースを厳しく制限(コンパイルエラーに)しているよ、ってことだと思います(感想 このPRの疑問に答えるにはこれで良いと思います、間違ってたらすいません・・・ |
|
もっと具体的な例を持ってきてみて auto f()
{
return [](int n = 3) { return n; };
}
int main()
{
auto ff = f();
ff();
ff(4);
}こんな感じでlambda式ごと外に持っていけるけどそのとき上の例の 関数の引数スコープってのがいまいちピンときてなかったのですが、呼び出し側のスコープで考えると理解すればいいのだろうか・・・。 |
そうですね、多分このサンプルコードが言いたいのはそういうことで、odr-usableはそういう状況を弁別するための概念というか道具だと思います。 もし仮にこれらのサンプルコードが適格だとすると、暗黙の参照キャプチャのような事が行われることになると思うので、それを考えると不適格とされているのはなぜダメで適格なのはなぜ良いのか理解しやすいかなあと思います。
ここでのfunction parameter scope(上の投稿では関数パラメータスコープと呼んでいます)とは、単純に関数の引数の名前が(シャドウイングされずに)参照可能なスコープの事です。それは関数引数宣言の点から、その関数の定義の終端までの範囲になります。 void f(
int a1, // a1の関数パラメータスコープの開始
int a2 // a2の関数パラメータスコープの開始
) {
// a1, a2の関数パラメータスコープの途中
{
int a1; // ブロックスコープ変数a1のスコープの開始
// 関数引数a1の関数パラメータスコープの中断
} // ブロックスコープ変数a1のスコープの終了
// 関数引数a1の関数パラメータスコープの再開
} // a1, a2の関数パラメータスコープの終了これは多分、宣言領域(declarative region)の考え方と同じで、関数パラメータスコープとは関数引数の宣言領域の事だと思います。 |
|
ご参考までに: function parameter scope も [basic.scope.param] で定義されています。 |
|
なるほど・・・。 PRの内容も再整理が必要そうですね・・・。 |
ref:
疑問点
ここでnがodr-usableではない理由が理解できていないです。
関数fの定義スコープは
nの宣言領域に含まれるはずです。またcapureは=なのでdefault-capureです。コメントでは
outside the block scope of the lambda-expressionとあるのですが、lambda-expressionのblock scopeとはどこでしょうか?言い換えると↓の部分をどう解釈したらいいかわかっていません。
https://timsong-cpp.github.io/cppwp/n4861/expr.prim.lambda
を
block scopeでgrepしましたがそれらしい文面がありませんでした。