51

Here is my code:

class test {
public:
    constexpr test() {
            
    }
        
    constexpr int operator+(const test& rhs) {
        return 1;
    }
};

int main() {
    
    test t;                         //constexpr keyword 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. In my first attempt which is using test, did it work because I used constexpr on the constructor already? If that is the case, then, is it correct to assume that all classes that have constexpr constructors will result in all objects that are instantiated or created with these constructors to be constexpr?

Bonus question:

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

Carlos Miguel Colanta
  • 2,685
  • 3
  • 31
  • 49
  • 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 – Nikos Athanasiou Jul 13 '15 at 08:24
  • 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. – cpplearner Jul 13 '15 at 08:31

3 Answers3

28

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
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
15

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).

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • 1
    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. – dyp Jul 13 '15 at 08:43
  • @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 ? – Nikos Athanasiou Jul 13 '15 at 09:08
  • 1
    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. – dyp Jul 13 '15 at 09:53
5

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.

Community
  • 1
  • 1
Ragora
  • 149
  • 9
  • 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. – nwp Jul 13 '15 at 10:52
  • Ah, alright, it made sense that it would require them all to evaluate statically. – Ragora Jul 13 '15 at 21:18