Rokiのチラ裏

学生による学習のログ

implicitly capture の振る舞い

以下の例が成り立つ。

const int i = 123; // #1
const float f = 123.f; // #2

[]{ i; }(); // OK
[]{ f; }(); // ill-formed

C++17 のラムダ式における reaching scope に関する記述 n4659/[expr.prim] 3, 7, n4659/[expr.prim.lambda.capture] 8 から引用。太文字は強調。

A lambda-expression whose smallest enclosing scope is a block scope is a local lambda expression; any other lambda-expression shall not have a capture-default or simple-capture in its lambda-introducer. The reaching scope of a local lambda expression is the set of enclosing scopes up to and including the innermost enclosing function and its parameters. [ Note: This reaching scope includes any intervening lambda-expressions.  — end note ]
(snip)
A lambda-expression with an associated capture-default that does not explicitly capture *this or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture's associated non-static data member), is said to implicitly capture the entity (i.e., *this or a variable) if the compound-statement:
— odr-uses the entity (in the case of a variable),
— odr-uses this (in the case of the object designated by *this), or
— names the entity in a potentially evaluated expression where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.
[ Example:
void f(int, const int (&)[2] = {})    { }   // #1
void f(const int&, const int (&)[1])  { }   // #2
void test() {
  const int x = 17;
  auto g = [](auto a) {
    f(x);                       // OK: calls #1, does not capture x
  };

  auto g2 = [=](auto a) {
    int selector[sizeof(a) == 1 ? 1 : 2]{};
    f(x, selector);             // OK: is a dependent expression, so captures x
  };
}
— end example ] All such implicitly captured entities shall be declared within the reaching scope of the lambda expression. [ Note: The implicit capture of an entity by a nested lambda-expression can cause its implicit capture by the containing lambda-expression (see below). Implicit odr-uses of this can result in implicit capture.  — end note ]
(snip)
If a lambda-expression or an instantiation of the function call operator template of a generic lambda odr-uses this or a variable with automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression.

続いて  odr-used に関する記述、同ドラフト [basic.def.odr] 3

A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless applying the lvalue-to-rvalue conversion to x yields a constant expression (snip) or e is a discarded-value expression.

続いて  constant-expression に関する記述、同ドラフト[expr.const] 2.7.1 *1

— an lvalue-to-rvalue conversion unless it is applied to
— a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
— a non-volatile glvalue that refers to a subobject of a string literal, or
— a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
— a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

まとめ

ブロックスコープ内のラムダの場合、reaching scope 内の特定の基準を満たす変数はキャプチャされなくともラムダ内では限られた用途で使用できる。reaching scope にはラムダが定義された時点でスコープ内にある、ラムダを含む関数のローカル変数が含まれる。特定の基準と限られた用途とは以下の通りである。

  1. ラムダの内部で変数は  odr-used であってはならない。つまり
    •  discarded-value\ expression または
    • 値の取り出し
  2. 変数は次のいずれかでなければならない。
    • 初期化子が  constant-expression である 非volatileconstな整数、enum、または
    • volatileconstexprな変数、及びそのようなサブオブジェクト


初めに示された例で、#1 は  constant-expression であり、かつ内部での利用方法は  discarded-value\ expression であるので  odr-used でない。よって implicitly capture が有効である。#2 は #1 と同じくラムダの reaching scope の対象とはなるものの、2 を満たさないため、#2 は implicitly capture の対象とはならない。

see also

*1:過去に const floating-point を constant expression にする要求を EWG が CWG に要請した事があるが拒否されている