C++:为什么这个constexpr不是编译时常量?

12
在以下的C++11代码中,最后一次对arraySize的调用会导致编译错误。显然这是因为y是一个运行时大小的数组,而无法为y推导出arraySize模板参数N。我不明白为什么x是编译时大小的数组,而y最终以运行时大小呈现。arraySize模板函数直接取自Scott Meyers的《Effective Modern C++》第1条款。
#include <cstddef>

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T(&)[N]) noexcept { return N; }

struct S
{
    char c[10];
};

int main()
{
    S s;
    S* ps = &s;

    char x[arraySize(s.c)];
    char y[arraySize(ps->c)]; // why is y a runtime sized array?

    arraySize(x);
    arraySize(y); // error !?

    return 0;
}

为了让它变得更暗一些:如果你将 arraySize() 的参数设为一个常量引用,那么以下代码将编译通过:char y[arraySize(decltype(ps->c){})]; - Quentin
相关 - P0W
@Quentin:嗯,你的替换是编译时常量表达式,而被替换的不是。这并不奇怪。 - Deduplicator
@Quentin:你知道decltype是一个未求值的上下文吗?如果省略大括号初始化器,它可能也不起作用。如果编译器真的接受arraySize(s.c)作为编译时常量表达式,我不知道为什么... - Deduplicator
显示剩余4条评论
1个回答

10

在C++中,错误不是对arraySize(y)的调用,而是y本身的声明。

数组声明中的边界必须是“转换后的常量表达式”。

如果您的编译器接受y的声明,并告诉您y是运行时绑定的数组,则它不是C++编译器。 在任何经过批准的C++版本或当前草案中都不存在运行时绑定的数组。

arraySize(s.c)arraySize(ps->c)之间的重要区别在于ps->c(*ps).c相同,且引用需要在ps上进行左值到右值转换,这不是常量表达式(&s也是如此)。 表达式的其余部分不涉及左值到右值转换,数组左值直接被引用所绑定。

常量表达式是指,其值是指向实体的glvalue核心常量表达式,该实体是常量表达式的允许结果(如下定义),或者是值为对象的prvalue核心常量表达式,在该对象及其子对象上:

  • 每个非静态数据成员的引用类型都指向允许作为常量表达式结果的实体,且

  • 如果该对象或子对象是指针类型,则它包含具有静态存储期的对象的地址,超出此类对象的末尾的地址(5.7),函数的地址或空指针值。

如果一个实体是具有静态存储期的对象,它既不是临时对象也满足上述约束条件,或者它是函数,则它是常量表达式的允许结果。

很明显,ps 包含的是具有自动存储期限的对象的地址,因此它不能被声明为 constexpr。但是,如果你将 S s; S* ps = &s; 改为 static S s; constexpr S* ps = &s;,所有事情都应该可以开始运转。

另一方面,arraySize(s.c) 的参数似乎也不是常量表达式,因为它是一个引用而不是静态存储期限的对象。


1
如果你的编译器接受了变量y的声明,并且后来告诉你y是一个运行时绑定的数组,那么这不是一个C++编译器。但实际上,符合规范的实现(编译器)可以通过扩展方式“接受”(即编译和/或执行)不合法的输入,例如在N4296草案中的1.4/8中所述。在这种情况下,实现必须发出诊断警告,而GCC和clang在未添加-pedantic选项的情况下无法发出变长数组的警告。 - Arne Vogel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接