1. ホーム
  2. c++

const変数をラムダで捕捉する必要がない場合があるのはなぜか?

2023-08-16 17:11:02

質問

次のような例を考えてみましょう。

#include <cstdlib>

int main() {
    const int m = 42;
    [] { m; }(); // OK

    const int n = std::rand();
    [] { n; }(); // error: 'n' is not captured
}

なぜ n を捕捉する必要があるのに、2番目のラムダでは m はないのですか?5.1.2項を確認したところ、( ラムダ式 ) を調べましたが、理由を見つけることができませんでした。このことが説明されている段落を教えていただけませんか。

更新: 私はこの動作を GCC 6.3.1 と 7 (trunk) の両方で観察しました。Clang 4.0 と 5 (trunk) では、両方のケースでエラーで失敗しました ( variable 'm' cannot be implicitly captured in a lambda with no capture-default specified ).

どのように解決するのですか?

ブロックスコープでのラムダでは、ブロックスコープ内で特定の条件を満たす変数が スコープに到達する にある特定の条件を満たす変数は、たとえそれがキャプチャされていなくても、ラムダ内部で限定的に使用することができる。

大雑把に言うと スコープに到達 には、ラムダを含む関数のローカル変数で、ラムダが定義された時点でスコープ内にあるものが含まれます。 つまり、これには mn を追加しました。

"ある基準"と"限定された方法"は、具体的には(C++14時点)です。

  • ラムダ内部で、変数が odr-usedです。 以外の操作を受けてはならないことを意味します。
    • が捨て値表現として現れること ( m; はその一つ)、または
    • その値が取得されること。
  • 変数はどちらかでなければなりません。
    • A constvolatile 整数またはenumでイニシャライザーが 定数式 または
    • A constexprvolatile 変数(またはそのサブオブジェクト)

C++14 への参照: [expr.const]/2.7, [basic.def.odr]/3 (最初の文), [expr.prim.lambda]/12, [expr.prim.lambda]/10.。

これらのルールの根拠は、他のコメント/回答で示唆されているように、コンパイラはブロックから独立した自由関数として捕捉なしラムダを "synthesize" できる必要があるからです (そのようなものは関数へのポインタに変換されることができるので)。それは変数が常に同じ値を持っているだろうことを知っていれば、変数を参照してもこれを実行できますし、コンテキストから独立して変数値を得るための手順を繰り返すこともできます。 しかし、変数が時々刻々と変化する可能性がある場合や、変数のアドレスが必要な場合などには、このようなことは行えません。


あなたのコードでは n は定数でない式で初期化されていました。 したがって n は捕捉されることなくラムダで使用することはできません。

m は定数式で初期化された 42 で初期化されているので、quot;certain criteria"を満たしています。捨て値式は式をodr-useしないので m; がなくても m は捕捉されません。


この2つのコンパイラの違いは、clangが考慮するのは m; をodr-useにすることです。 m を使いますが、gccは使いません。 basic.def.odr]/3 の最初の文は、かなり複雑です。

変数 x で、その名前が評価される可能性のある式として表示されます。 ex odr-usedです。 によって ex にlvalueからrvalueへの変換を適用しなければ x が自明でない関数を呼び出さない定数式を生成し、もし x がオブジェクトであれば ex は式の結果の候補の集合の要素です。 e に lvalue から rvalue への変換が適用される場合です。 e に適用されるか、あるいは e は捨て値の式です。

が、よく読むと、discarded-value expressionは、以下のようなことはしないと明記されています。 odr-use を使用しないことが明記されています。

C++11 の [basic.def.odr] バージョンにはもともと廃棄値式のケースが含まれていないため、公開されている C++11 では clang の動作は正しいでしょう。 しかし、C++14 で表示されているテキストは、C++11 に対する欠陥として受け入れられました ( 課題712 ) のため、コンパイラーは C++11 モードであってもその動作を更新する必要があります。