在构造函数上指定 constexpr 是否会自动使从它创建的所有对象成为 constexpr?

Does specifying constexpr on constructor automatically makes all objects created from it to be constexpr?

这是我的代码:

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 执行相同操作来复制结果,但出现错误。具体来说,它希望 constexpr int c = w + 2; 中的 int wconstexpr。从我第一次尝试使用 test 开始,它是否起作用是因为我已经在构造函数上使用了 constexpr?如果是这种情况,那么假设所有在其构造函数上具有 constexpr 的 类 将导致所有用它实例化或创建的对象都是 constexpr 是不是很好?

奖金问题:

如果我有一个 constexpr 构造函数,做类似的事情是不是很糟糕? test * t = new test(); ?

根据我的实验 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,但在这种情况下,您真的应该咨询有关您的编译器的细节。

constexpr 构造函数对 class 类型的影响可以在 C++ 标准中阅读

3.9 Types

(...)

  1. A type is a literal type if it is:

    • it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template that is not a copy or move constructor

(...)

所以 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 是一个 字面值 class 并不意味着它的所有实例都是常量表达式:

#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;
}

Demo

在上面的例子中,实例 a 没有被声明为一个常量,所以即使 a 可以是一个 constexpr 常量,它不是一个(因此它可以被修改) .

拥有 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 说:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression [...]

并包括以下项目符号:

  • an invocation of a function other than a constexpr constructor for a literal class or a constexpr function [ Note: Overload resolution (13.3) is applied as usual —end note ];

[...]

  • an invocation of a constexpr constructor with arguments that, when substituted by function invocation substitution (7.1.5), do not produce all constant expressions for the constructor calls and full-expressions in the mem-initializers

  • an invocation of a constexpr function or a constexpr constructor that would exceed the implementationdefined recursion limits (see Annex B);

通过引入成员变量 x:

test 做一些小改动,我们可以更容易地看到这一点
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] 中的例外之一:

  • an lvalue-to-rvalue conversion (4.1) unless it is applied to

    [...]

    • a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or