这是我的代码:
class test{
public:
constexpr test(){
}
constexpr int operator+(const test& rhs){
return 1;
}
};
int main(){
test t; //constexpr word isn't necessary
constexpr int b = t+test(); // works at compile time!
int w = 10; // ERROR constexpr required
constexpr int c = w + 2; // Requires w to be constexpr
return 0;
}
我注意到即使我没有将测试指定为constexpr
,它也能正常工作。我尝试通过对int
执行相同的操作来复制结果,但是我得到了错误。具体来说,它希望int w
内的constexpr int c = w + 2;
为constexpr
。从我第一次尝试使用test
开始,是否因为我在构造函数上使用constexpr
的原因才有效?如果是这种情况,那么可以假设所有在其构造函数上具有constexpr
的类将导致所有实例化或使用它创建的对象为constexpr
吗?
奖金问题:
如果我有一个constexpr
构造函数,那么做某事是不是很糟糕? test * t = new test();
?
答案 0 :(得分:16)
有一个constexpr构造函数不会自动声明该变量constexpr,因此t
不是constexpr。在这种情况下发生的是你正在调用constexpr函数,这一行:
constexpr int b = t+test();
可以看作如下:
constexpr int b = t.operator+( test() );
那么问题是test()
是否是一个常量表达式,因为构造函数是constexpr并且不属于草案C ++ 11标准部分5.19
下的任何例外情况。 [expr.const] 第2
段说:
条件表达式是核心常量表达式,除非它 涉及以下之一作为潜在评估的子表达式 [...]
并包含以下项目符号:
- 为文字类或constexpr函数调用除constexpr构造函数之外的函数[注意:重载决策 (13.3)照常使用 - 后注];
[...]
调用带有参数的constexpr构造函数,当被函数调用替换时 替换(7.1.5),不为构造函数调用生成所有常量表达式 mem-initializers中的完整表达式
调用constexpr函数或constexpr构造函数,它们将超出实现的定义 递归限制(见附件B);
我们可以通过引入成员变量test
对x
进行一些小的更改来更容易地看到这一点:
class test{
public:
constexpr test(){
}
constexpr int operator+(const test& rhs) const {
return x + 1 ;
}
int x = 10 ;
};
试图在operator +
中访问它,我们可以看到以下行现在失败了:
constexpr int b = t+test();
来自clang( see it live )的以下错误:
error: constexpr variable 'b' must be initialized by a constant expression
constexpr int b = t+test(); // works at compile time!
^ ~~~~~~~~
note: read of non-constexpr variable 't' is not allowed in a constant expression
return x + 1 ;
^
失败是因为t
不是constexpr变量,因此它的子对象也不是constexpr变量。
你的第二个例子:
constexpr int c = w + 2;
不起作用,因为它属于草案C ++ 11标准部分5.19
[expr.const] 中的一个例外:
左值 - 右值转换(4.1),除非它适用于
[...]
- 一个整数或枚举类型的glvalue,它引用具有前面初始化的非易失性const对象,已初始化 用一个常量表达式,或
答案 1 :(得分:10)
可以在C ++标准中读取constexpr
构造函数对类类型的影响
3.9类型
(...)
- 醇>
如果类型为:
,则类型为文字类型
- 它是一个聚合类型(8.5.1)或至少有一个constexpr构造函数或构造函数模板,它不是复制或移动构造函数
(...)
所以constexpr
构造函数意味着可以执行静态初始化,并且可以像this one一样使用:
#include <iostream>
struct test {
int val;
constexpr test(int val) : val(val) { }
};
template<int N>
struct CC {
double m[N];
};
int main()
{
CC<test(6).val> k; // usage where compile time constant is required
std::cout << std::end(k.m) - std::begin(k.m) << std::endl;
return 0;
}
仅test
是文字类 这一事实并不意味着它的所有实例都是常量表达式:
#include <iostream>
struct test {
int val;
constexpr test(int val) : val(val) { }
};
int main()
{
test a(1);
++a.val;
std::cout << a.val << std::endl;
return 0;
}
在上面的示例中,实例a
未被声明为常量,因此即使a
可能是constexpr
常量,也不是一个(因此可以修改)。< / p>
答案 2 :(得分:3)
我的实验in this answer的constexpr关键字或多或少地指示编译器必须能够静态地解析该调用中给出的所有代码路径。也就是说,至少现在(它会出现),一切都必须沿着该代码路径声明constexpr,否则它将失败。例如,在您的代码中,如果您没有声明运算符或构造函数constexpr,则对b的初始constexpr赋值将失败。似乎constexpr仅在您分配给声明为constexpr的变量时才生效,否则它似乎只是作为编译器的顾问,可以通过静态评估来优化代码路径,但它“#”;如果你没有用constexpr变量赋值显式地指示它,则不能保证这样做。
话虽如此,似乎在正常情况下声明构造函数constexpr没有任何影响。下面的机器代码是使用以下命令行生成的:
g++ -std=c++11 -Wall -g -c main.cpp -o obj/Debug/main.o
g++ -o bin/Debug/TestProject obj/Debug/main.o
所以你的b作业产生了这个代码:
0x4005bd push rbp
0x4005be mov rbp,rsp
0x4005c1 mov DWORD PTR [rbp-0x4],0x1
0x4005c8 mov eax,0x0
0x4005cd pop rbp
0x4005ce ret
但是,如果删除b变量上的constexpr声明:
0x4005bd push rbp
0x4005be mov rbp,rsp
0x4005c1 sub rsp,0x10
0x4005c5 lea rax,[rbp-0x5]
0x4005c9 mov rdi,rax
0x4005cc call 0x4005ee <test::test()>
0x4005d1 lea rdx,[rbp-0x5]
0x4005d5 lea rax,[rbp-0x6]
0x4005d9 mov rsi,rdx
0x4005dc mov rdi,rax
0x4005df call 0x4005f8 <test::operator+(test const&) const>
0x4005e4 mov DWORD PTR [rbp-0x4],eax
0x4005e7 mov eax,0x0
0x4005ec leave
0x4005ed ret
它似乎被处理为好像操作符和构造函数没有被声明为constexpr,但是在这种情况下你应该查阅有关编译器的细节,实际上。