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

29,115

Solution 1

Having a constexpr constructor does not make declarations of that variable automatically constexpr, so t is not a constexpr. What is going on in this case is that you are calling a constexpr function, this line:

constexpr int b = t+test(); 

can be viewed as follows:

constexpr int b = t.operator+( test() ); 

So then the question is whether test() is a constant expression, which it is since the constructor is constexpr and does not fall under any of the exceptions under the draft C++11 standard section 5.19 [expr.const] paragraph 2 which says:

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

and includes the following bullet:

  • 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);

We can see this more readily by making some small changes to test by introducing a member variable x:

class test{
    public:
    constexpr test(){

    }

    constexpr int operator+(const test& rhs) const {
        return x + 1  ;
    }

    int x = 10 ;
};

Attempting to access it in operator + and we can see that the following line now fails:

constexpr int b = t+test();

with the following error from 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  ;
           ^

It fails because t is not a constexpr variable and therefore its subobjects are also not constexpr variables.

Your second example:

 constexpr int c = w + 2;  

does not work because it falls under one of the exceptions in the draft C++11 standard section 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

Solution 2

The effect that a constexpr constructor has on the class type can be read in the C++ Standard

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

(...)

So constexpr constructors means that static initialization can be performed and uses like this one are possible :

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

The mere fact that test is a literal class does not mean all its instances will be constant expressions:

#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

In the example above the instance a was not declared as a constant so even though a could be a constexpr constant, it's not one (hence it can be modified).

Solution 3

The constexpr key word by my experiments in this answer more or less instructs the compiler that it must be able to statically resolve all codepaths given in that call. That is, at least right now (it would appear), everything must be declared constexpr along that codepath otherwise it will fail. For example, in your code, the initial constexpr assignment to b will fail if you don't declare the operator or constructor constexpr. It appears that the constexpr only takes effect when you're assigning to a variable that is declared constexpr, otherwise it seems to only serve as an adviser to the compiler that the codepath can be optimized via static evaluation, but it's not guaranteed to do it if you don't explicitly instruct it with a constexpr variable assignment.

That being said, it would appear that declaring a constructor constexpr has no affect under normal circumstances. The machine code below was produced with the following command line:

g++ -std=c++11 -Wall -g  -c main.cpp -o obj/Debug/main.o
g++  -o bin/Debug/TestProject obj/Debug/main.o  

And so your b assignment produces this code:

0x4005bd    push   rbp
0x4005be    mov    rbp,rsp
0x4005c1    mov    DWORD PTR [rbp-0x4],0x1
0x4005c8    mov    eax,0x0
0x4005cd    pop    rbp
0x4005ce    ret

However, if you remove the constexpr declaration on the b variable:

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

It appears to be handled as if the operator and constructor weren't declared constexpr, but this is a situation where you should consult the specifics about your compiler, really.

Share:
29,115

Related videos on Youtube

Carlos Miguel Colanta
Author by

Carlos Miguel Colanta

Aspiring C++ programmer.

Updated on July 09, 2022

Comments

  • Carlos Miguel Colanta
    Carlos Miguel Colanta almost 2 years

    Here is my code:

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

    I notice that it worked even though I didn't specify test to be constexpr. I tried replicating the result by doing the same with int but i get errors. Specifically, it wants my int w inside the constexpr int c = w + 2; to be constexpr. From my first attempt which is using test, Did it work because of the reason that i used constexpr on the constructor already? If that is the case then would it good to assume that all classes that have constexpr on their constructors will result to all objects instantiated or created with it to be constexpr?

    Bonus question:

    If I have a constexpr constructor, is it bad to do something like ? test * t = new test(); ?

    • Nikos Athanasiou
      Nikos Athanasiou almost 9 years
      If you write a context where the instance is not constexpr, your class simply won't be used as a literal class. See my answer below for details
    • cpplearner
      cpplearner almost 9 years
      Your operator+ does not access the value of its operands (or, in a language-lawyer's view, it does not perform "lvalue-to-rvalue conversion" on its parameters), but int's addition operator does.
  • dyp
    dyp almost 9 years
    Also, the equation is no constexpr constructor => not a literal type, which is not the same as a constexpr constructor => a literal type. There can be classes with a constexpr constructor which are not literal types.
  • Nikos Athanasiou
    Nikos Athanasiou almost 9 years
    @dyp I usually saw constexpr constructors with classes that worked with user defined literals and subsequently were used as literals. My intention to talk about literals had no explanation in the body of the answer (+ some clumsy wording). Since there's no need to refer to that I removed the poorly worded references and kept the literal class bit. Any suggestions ?
  • dyp
    dyp almost 9 years
    Huh. For all I know, there's no relation between (user-defined) literals and literal types. They use the same word, but that looks like just another unfortunate naming confusion in the standard. Literal types are, in a way, very simple types. The property being literal for a type allows for certain uses within constant expressions. -- The fact that constexpr constructors are required for static init also applies to non-literal types, see [basic.start.init]p2.
  • nwp
    nwp almost 9 years
    It is actually enough that one code path exists that can be statically resolved. Of course you do not get static execution with a path that cannot be statically evaluated.
  • Ragora
    Ragora almost 9 years
    Ah, alright, it made sense that it would require them all to evaluate statically.