web-dev-qa-db-ja.com

const変数をラムダでキャプチャする必要がない場合があるのはなぜですか?

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

#include <cstdlib>

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

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

最初のラムダのnではなく、2番目のラムダのmをキャプチャする必要があるのはなぜですか? C++ 14標準のセクション5.1.2(ラムダ式)をチェックしましたが、理由を見つけることができませんでした。これが説明されている段落を教えていただけますか?

更新:GCC 6.3.1と7(トランク)の両方でこの動作を観察しました。 Clang 4.0および5(トランク)は、両方の場合にエラーで失敗します(variable 'm' cannot be implicitly captured in a lambda with no capture-default specified)。

70
s3rvac

ブロックスコープのラムダの場合、リーチスコープの特定の基準を満たす変数は、キャプチャされなくても、ラムダ内で制限された方法で使用できます。

大まかに言って、スコープに達するには、ラムダを含む関数のローカル変数が含まれます。これは、ラムダが定義された時点でスコープ内にあります。したがって、上記の例ではmnが含まれます。

「特定の基準」と「制限された方法」は、具体的には次のとおりです(C++ 14以降)。

  • ラムダ内では、変数はodr-usedであってはなりません。つまり、次の操作を除いてはならないということです。
    • 破棄された値の式として表示される(m;はこれらのいずれか)、または
    • 値を取得します。
  • 変数は次のいずれかでなければなりません。
    • 初期化子が定数式であったconst、非volatile整数または列挙、または
    • constexpr、非volatile変数(またはそのようなサブオブジェクト)

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

他のコメント/回答で示唆されているように、これらのルールの理論的根拠は、コンパイラーが、キャプチャーされていないラムダをブロックから独立したフリー関数として「合成」できる必要があるということです(そのようなものはポインターに変換できるため、機能する);変数が常に同じ値を持つことがわかっている場合、変数を参照しているにもかかわらずこれを行うことができます。または、コンテキストに関係なく変数の値を取得する手順を繰り返すことができます。しかし、変数が時々異なる場合、または変数のアドレスが必要な場合など、これを行うことはできません。


コードでは、nが非定数式によって初期化されました。したがって、nは、キャプチャせずにラムダで使用することはできません。

mは定数式42で初期化されたため、「特定の基準」を満たします。破棄された値の式は式をODRで使用しないため、m;mをキャプチャせずに使用できます。 gccは正しいです。


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

名前が潜在的に評価される式xとして表示される変数exは、exは、左辺値から右辺値への変換をxに適用しない限り、重要な関数を呼び出さない定数式を生成し、xがオブジェクトの場合、exeの潜在的な結果のセットの要素です。左辺値から右辺値への変換がeに適用されるか、eが破棄された値です表現。

しかし、詳しく読むと、廃棄値式は式をodr-useしないことを具体的に述べています。

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

50
M.M

定数式であるため、コンパイラは[] { 42; }();であるかのように処理します

[ expr.prim.lambda ]のルールは次のとおりです。

ラムダ式または汎用ラムダの関数呼び出し演算子テンプレートのインスタンス生成が、このスコープまたは到達範囲からの自動ストレージ期間を持つ変数(3.2)を使用する場合、そのエンティティはラムダ式によってキャプチャされます。

ここで、標準[ basic.def.odr ]からの引用:

名前が潜在的に評価される式exとして表示される変数xは、xに左辺値から右辺値への変換を適用して定数式(...)が生成されるか、eが破棄値式である場合を除き、odrが使用されます。

(短くするためにそれほど重要ではない部分を削除しました)

私の簡単な理解は:コンパイラはmがコンパイル時に定数であることを知っているのに対し、nは実行時に変化するため、nをキャプチャする必要があります。 nはodrで使用されます。実行時にnの中身を実際に確認する必要があるためです。言い換えると、nの定義は「1つしか存在できない」という事実に関連しています。

これはM.Mのコメントからです:

mは、定数式初期化子を含むconst自動変数であるため定数式ですが、nはその初期化子が定数式ではないため、定数式ではありません。これは[expr.const] /2.7で説明されています。 [basic.def.odr]/3の最初の文によると、定数式はODRで使用されません

デモ についてはこちらをご覧ください。

33
Beginner

編集:私の答えの以前のバージョンは間違っていました。初心者の方が正しいです、ここに関連する標準的な引用があります:

[basic.def.odr]

  1. 名前が潜在的に評価される式exとして表示される変数xは、 lvalue-toを適用しない限り、exによってodr-usedです-rvalue xへの変換により、 定数式 が生成されます。この式は、重要な関数を呼び出さず、xがオブジェクトの場合、exは集合の要素です。式eの潜在的な結果のリスト。左辺値から右辺値への変換がeに適用されるか、eが廃棄値式です。 ...

mは定数式であるため、odrを使用しないため、キャプチャする必要はありません。

Clangsの動作は標準に準拠していないようです。

2
eerorika