我有两个摘要。
第一段代码:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
if constexpr (foo(a)) {
}
}
第二个片段:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
std::string& x = a;
if constexpr (foo(x)) {
}
}
第一个编译,但是第二个不编译(错误消息:错误:'x'的值不能在常量表达式中使用。为什么?为什么a
可以在常量表达式中使用,而x
不能使用?
该命令,用于编译g++ -std=c++17 main.cpp
。
答案 0 :(得分:4)
因为通常一个常量表达式不能评估引用具有自动存储持续时间的对象的引用。在这里,我的意思是通过确定对象的identity来“评估”,而不是通过确定对象的值来不是。因此,即使您的示例中甚至不需要对象a
的值(即未应用左值到右值转换),foo(x)
仍不是常数表达式。
注意foo(a)
不评估任何参考。尽管foo
的参数是一个引用,但它不会被视为表达式。实际上,例如,即使对其进行了评估,
template <typename T>
constexpr bool foo(T&& t) {
t;
return false;
}
foo(a)
仍然是一个常量表达式。此类情况是例外,因为引用t
在foo(a)
的评估内初始化。
标准中的相关部分(无关的部分被我删除了)
表达式e是核心常量表达式,除非按照抽象机的规则对e的求值将对以下表达式之一求值:
...
一个id表达式,它引用引用类型的变量或数据成员,除非引用具有前面的初始化,并且其中一个都为
- 初始化
使用常量表达式或
其寿命始于e的评估;
...
常量表达式是glvalue核心常量表达式,它引用的实体是常量表达式(如下定义)的允许结果,... 如果实体是具有静态存储持续时间的对象,该对象不是临时对象,或者是其值满足上述条件的临时对象,则它是常量表达式的许可结果约束,或者它是一个函数。
答案 1 :(得分:2)
由于您无法在编译时知道x
的值,因此您所知道的就是它将指向a
。您正在执行的操作是检查x
的“值”,但是x
的值是a
所在的地址,您不知道a
的位置分配的地址也不是常量。
另一方面,您已经知道a
的值,它是一个空的std :: string。
此问题包含更多详细信息:how to initialize a constexpr reference
答案 2 :(得分:0)
首先,在您的两个代码部分中,评估上下文均要求postfix-expression为常量表达式。功能模板foo
的定义满足constexpr function
的要求,因为其参数是文字类型。
id-expression a
和x
都是glvalue,它们的评估确定对象的身份,但是唯一的区别在于,在您的第一手代码中。由于以下规则,因此从a
复制初始化参数仅需要身份转换:
当引用类型的参数直接绑定到参数表达式时,隐式转换序列为 identity conversion ,除非参数表达式具有作为参数类型派生类的类型,否则在这种情况下,隐式转换序列是从派生到基础的转换
由于
,它什么也没做
[over.ics.scs#2]
如条款[conv]中所述,标准转换顺序是身份转换本身(即,不转换)
其余的表达式都是常量表达式。因此,对于foo(a)
这个表达式,它是一个常数表达式。
对于foo(x)
,因为x
是引用类型的ID表达式,但必须遵守以下规则:
一个id表达式,它引用引用类型的变量或数据成员,除非该引用具有前面的初始化且具有
- 使用常量表达式或
进行初始化- 其寿命始于e的评估;
x
的生存期始于对foo(x)
的求值之前,由于std::string a;
不是glvalue常量表达式,因此未使用常量表达式进行初始化。如果将std::string a;
修改为static std::string a;
,就可以了。