我似乎无法理解为什么下面的类型为const int的代码编译:
int main()
{
using T = int;
const T x = 1;
auto lam = [] (T p) { return x+p; };
}
$ clang++ -c lambda1.cpp -std=c++11
$
而这个类型为const double的那个不是:
int main()
{
using T = double;
const T x = 1.0;
auto lam = [] (T p) { return x+p; };
}
$ clang++ -c lambda2.cpp -std=c++11
lambda1.cpp:5:32: error: variable 'x' cannot be implicitly captured in a lambda with no capture-default specified
auto lam = [] (T p) { return x+p; };
^
lambda1.cpp:4:11: note: 'x' declared here
const T x = 1.0;
^
lambda1.cpp:5:14: note: lambda expression begins here
auto lam = [] (T p) { return x+p; };
^
1 error generated.
用constexpr double编译:
int main()
{
using T = double;
constexpr T x = 1.0;
auto lam = [] (T p) { return x+p; };
}
$ clang++ -c lambda3.cpp -std=c++11
$
为什么int的行为不同于double,或者对于int以外的任何其他类型,即int被const限定符接受,但double / other类型必须是constexpr?另外,为什么这段代码用C ++ 11编译,我从[1]的理解是这种隐式捕获是C ++ 14特性。
.. [1] how is this lambda with an empty capture list able to refer to reaching-scope name?
答案 0 :(得分:14)
这样做的原因是为了保持C ++ 03兼容性,因为在C ++ 03中使用常量表达式初始化的const整数或const枚举类型可用于常量表达式,但浮点数不是这种情况。
保持限制的基本原理可以在C ++ 11之后的defect report 1826中找到(这解释了ABI中断评论)并询问(强调我的) EM>):
用常量初始化的const整数可以用在常量表达式中,但是用常量初始化的const浮点变量不能。这个是故意的,与C ++ 03兼容,同时鼓励一致使用constexpr 。然而,有些人发现这种区别是令人惊讶的。
还观察到允许const浮点变量作为常量表达式将是ABI突破性变化,因为它会影响lambda捕获。
一种可能性是在常量表达式中不推荐使用const积分变量。
并且回复是:
CWG认为当前的规则不应该改变,并且希望浮点值参与常量表达式的程序员应该使用constexpr而不是const。
我们可以注意到,问题指出允许 const浮点变量为常量表达式将是关于lambda捕获的ABI中断。
这是因为lambda不需要捕获变量,如果它没有使用odr并且允许const浮点变量是常量表达式将允许它们落在这个异常之下。
这是因为在常量表达式中允许使用常量表达式或constexpr文字类型初始化的const整数或枚举类型的左值到右值转换。对于使用常量表达式初始化的const浮点类型,不存在此类异常。这在C ++ 11标准草案[expr.const]p2草案中有所介绍:
条件表达式是核心常量表达式,除非它可能涉及以下之一 评估子表达式[...]
并包含在[expr.const]p2.9
中
- 左值 - 右值转换(4.1),除非适用于
- 一个整数或枚举类型的glvalue,它引用具有前面初始化的非易失性const对象,已初始化 用一个常量表达式,或
- 一个文字类型的glvalue,它指的是用constexpr定义的非易失性对象,或者指的是这样的子对象 对象,或
如果它们不再需要捕获非odr使用的const浮点值(这是ABI中断),则更改此值可能会影响lambda对象的大小。这个限制最初是为了保持C ++ 03的兼容性并鼓励使用constexpr,但现在这个限制已经到位,很难将其删除。
注意,在C ++ 03 we were only allowed to specify an in class constant-initializer for const integral or const enumeration types中。在C ++ 11中,这被扩展了,我们被允许使用大括号或等号初始化器为constexpr文字类型指定常量初始化器。
答案 1 :(得分:10)
根据标准§5.1.2/ p12 Lambda表达式[expr.prim.lambda]( Emphasis Mine ):
具有相关捕获默认值的lambda表达式 明确捕获
this
或具有自动存储持续时间的变量 (这排除了任何被发现引用的id表达式 据说是initcapture的相关非静态数据成员 隐含地捕获实体(即this
或变量) 复合声明:(12.1) - odr-uses(3.2)实体,或
(12.2) - 在一个可能被评估的表达式(3.2)中命名实体 封闭full-expression取决于通用lambda参数 在lambda表达式的范围内声明 [实施例:
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 }; }
- 结束示例]所有这些隐式捕获的实体都应该是 在lambda表达式的范围内声明。 [ 注意: 通过嵌套的lambda表达式隐式捕获实体可以 通过包含lambda-expression导致其隐式捕获(参见 下面)。隐含的odr用法可能导致隐式捕获。 - 结束说明]
这里的标准是,如果使用了odr,则需要捕获lambda中的变量。通过使用odr,标准意味着需要变量定义,因为它的地址被采用或者是对它的引用。
然而,这条规则有例外。其中一个特别感兴趣的是标准§3.2/ p3一个定义规则[basic.def.odr]( Emphasis Mine ):
变量x,其名称显示为可能已评估的表达式 ex除非应用左值到右值的转换,否则由ex使用 (4.1)到x产生一个不调用的常量表达式(5.20) 任何非平凡的函数,如果x是一个对象,ex是一个元素 表达式e,...的潜在结果集合
现在如果在例子中:
int main() {
using T = int;
const T x = 1;
auto lam = [] (T p) { return x+p; };
}
和
int main() {
using T = double;
constexpr T x = 1.0;
auto lam = [] (T p) { return x+p; };
}
在x
上对左值转换应用左值我们得到一个常量表达式,因为在第一个示例中x
是一个整数常量,在第二个示例中x
被声明为constexpr
}。因此,x
不需要在这些上下文中捕获。
但是,例子并非如此:
int main() {
using T = double;
const T x = 1.0;
auto lam = [] (T p) { return x+p; };
}
在此示例中,如果我们将左值应用于右值转换为x
,我们就不会得到常量表达式。
现在您可能想知道为什么会这样,因为x
是const double
。那么答案是,如果一个是常量整数或枚举类型,那么声明没有constexpr
的变量就可以作为常量表达式,并且在声明时用一个常量表达式初始化。 §5.20/ p2.7.1常量表达式[expr.const]( Emphasis Mine )中的标准证明了这一点:
条件表达式e是核心常量表达式,除非 评估e,遵循抽象机的规则(1.9), 将评估以下表达式之一:
...
(2.7) - 左值 - 右值转换(4.1),除非它适用于
(2.7.1) - 整数或枚举类型的非易失性glvalue 是指具有前面的完整的非易失性const对象 初始化,用常量表达式初始化,...
因此,需要捕获const double
变量,因为左值到右值的转换不会使常量表达式大喊。因此,理所当然地会出现编译错误。