以下constexpr函数无法编译:
constexpr void fnc() { constexpr int i = 5; constexpr auto ptr = &i; }
为什么ptr不能是constexpr,考虑到所有的计算都发生在一个constexpr函数中,并且它在没有声明ptr constexpr的情况下工作?
ptr
xxhby3vn1#
error message from clang非常清楚:非静态constexpr变量“i”的地址在每次调用封闭函数时可能不同;添加“static”到给予它一个常量地址
zf2sa74q2#
i是constexpr这一事实与其地址是否为常数的问题无关。constexpr对变量的作用在[dcl.constexpr] p6中解释:在任何constexpr变量声明中,初始化的完整表达式应该是常量表达式。一个constexpr变量仅仅由一个常量表达式初始化。每次调用fnc都会创建一个不同的对象i,所以地址可能会改变。一个对象需要有静态存储持续时间([expr.const] p13.3),其地址才能成为常量表达式:
i
constexpr
fnc
static constexpr int i = 5; constexpr auto ptr = &i; // OK
static constexpr
iswrvxsc3#
ptr的值是一个 core constant expression,这意味着它可以在常量求值期间求值和使用,但它不是一个 constant expression。常量表达式是具有一些附加限制的核心常量表达式。只有常量表达式可以是constexpr变量或非类型模板参数的最终值。(非类型模板参数有一些进一步的限制,我不会在这里讨论。指针类型的常量表达式可以指向具有静态存储期限的对象,或者超过具有静态存储期限的对象末尾的对象,但它不能指向具有任何其他存储期限的对象。见[expr.const]/13。直觉是指针类型的常量表达式必须表示一个地址,该地址的数值在链接时是已知的。(但不是在编译时,这就是为什么你不能在常量表达式中使用reinterpret_cast。)这个限制是人为的,可以删除;但是,它确实可以防止constexpr指针悬空:
reinterpret_cast
constexpr void fnc() { constexpr int i = 5; static constexpr auto ptr = &i; }
如果上面的代码可以编译,那么第二次调用fnc时,ptr将是悬空的,因为它仍然指向第一次调用fnc时创建的i的副本。建议P2686R2将使OP的代码有效,但上面的片段仍然无效:具有自动存储持续时间的constexpr指针/引用将被允许指向/引用在同一函数中较早声明的具有自动存储持续时间的对象,或者指向/引用具有静态存储持续时间的对象,但是具有静态存储持续时间的constexpr指针/引用将仅被允许指向/引用具有静态存储持续时间的对象。仍然不允许将具有自动存储持续时间的对象的重命名作为模板参数。
rqqzpn5f4#
我将尝试提供一个直观的解释,因为其他人已经很好地介绍了language-lawyer方面。i是constexpr,但它也是odr使用的,这意味着它必须在内存中给定一个地址。这就是对象身份。每个不同的示例都需要有一个不同的标识(在其生命周期内)。考虑:
constexpr void fnc(int depth = 1, int const* ptr2 = nullptr) { constexpr int i = 5; const auto ptr = &i; static_assert(ptr != ptr2); if (depth > 0) fnc(depth - 1, &i); }
当您调用fnc()时,深度== 1且ptr2 == nullptr递归调用的深度== 0,ptr2等于调用者中i的地址。ptr和ptr2应该相等吗?C++说“no”,每个都是不同const int对象的地址。但这也意味着指针值不是编译时常量。
fnc()
ptr2
const int
4条答案
按热度按时间xxhby3vn1#
error message from clang非常清楚:
非静态constexpr变量“i”的地址在每次调用封闭函数时可能不同;添加“static”到给予它一个常量地址
zf2sa74q2#
i
是constexpr
这一事实与其地址是否为常数的问题无关。constexpr
对变量的作用在[dcl.constexpr] p6中解释:在任何
constexpr
变量声明中,初始化的完整表达式应该是常量表达式。一个
constexpr
变量仅仅由一个常量表达式初始化。每次调用fnc
都会创建一个不同的对象i
,所以地址可能会改变。一个对象需要有静态存储持续时间([expr.const] p13.3),其地址才能成为常量表达式:
constexpr
函数中允许static constexpr
,请参见Can you declare static local variables in a constexpr function?*iswrvxsc3#
ptr
的值是一个 core constant expression,这意味着它可以在常量求值期间求值和使用,但它不是一个 constant expression。常量表达式是具有一些附加限制的核心常量表达式。只有常量表达式可以是constexpr
变量或非类型模板参数的最终值。(非类型模板参数有一些进一步的限制,我不会在这里讨论。指针类型的常量表达式可以指向具有静态存储期限的对象,或者超过具有静态存储期限的对象末尾的对象,但它不能指向具有任何其他存储期限的对象。见[expr.const]/13。
直觉是指针类型的常量表达式必须表示一个地址,该地址的数值在链接时是已知的。(但不是在编译时,这就是为什么你不能在常量表达式中使用
reinterpret_cast
。)这个限制是人为的,可以删除;但是,它确实可以防止constexpr指针悬空:如果上面的代码可以编译,那么第二次调用
fnc
时,ptr
将是悬空的,因为它仍然指向第一次调用fnc
时创建的i
的副本。建议P2686R2将使OP的代码有效,但上面的片段仍然无效:具有自动存储持续时间的constexpr指针/引用将被允许指向/引用在同一函数中较早声明的具有自动存储持续时间的对象,或者指向/引用具有静态存储持续时间的对象,但是具有静态存储持续时间的constexpr指针/引用将仅被允许指向/引用具有静态存储持续时间的对象。仍然不允许将具有自动存储持续时间的对象的重命名作为模板参数。
rqqzpn5f4#
我将尝试提供一个直观的解释,因为其他人已经很好地介绍了language-lawyer方面。
i
是constexpr
,但它也是odr使用的,这意味着它必须在内存中给定一个地址。这就是对象身份。每个不同的示例都需要有一个不同的标识(在其生命周期内)。考虑:
当您调用
fnc()
时,深度== 1且ptr2 == nullptr递归调用的深度== 0,ptr2等于调用者中
i
的地址。ptr
和ptr2
应该相等吗?C++说“no”,每个都是不同const int
对象的地址。但这也意味着指针值不是编译时常量。