I was playing around with constexpr
constructors in C++14 and above and noticed something strange. Here is my code:
#include <iostream>
#include <string>
using std::cout;
using std::endl;
#define PFN(x) cout << x << __PRETTY_FUNCTION__ << endl
#define PF PFN("")
#define NL cout << endl
struct A {
constexpr A() { PF; }
virtual ~A() { PF; NL; }
};
struct B : A {
constexpr B() { PFN(" "); }
virtual ~B() { PFN(" "); }
};
int main(int argc, char** argv) {
{ A a; }
{ B b; }
A* a = new B;
delete a;
return 0;
}
Simple enough example. I compiled it with g++ -std=c++14 -o cx_test cx_test.cpp
, expecting it to give me a compile error (because I am using cout
and the stream operator to print the function's name. But, to my surprise, it compiled! When I ran it, it gave the following output:
$> g++ -std=c++14 -o cx_test cx_test.cpp && ./cx_test
constexpr A::A()
virtual A::~A()
constexpr A::A()
constexpr B::B()
virtual B::~B()
virtual A::~A()
constexpr A::A()
constexpr B::B()
virtual B::~B()
virtual A::~A()
$>
But, when I compile with clang, I get:
$> clang++ -std=c++14 -o cx_test cx_test.cpp && ./cx_test
cx_test.cpp:12:15: error: constexpr constructor never produces a constant expression [-Winvalid-constexpr]
constexpr A() { PF; }
^
cx_test.cpp:12:21: note: non-constexpr function 'operator<<<std::char_traits<char> >' cannot be used in a constant expression
constexpr A() { PF; }
^
cx_test.cpp:9:12: note: expanded from macro 'PF'
#define PF PFN("")
^
cx_test.cpp:8:26: note: expanded from macro 'PFN'
#define PFN(x) cout << x << __PRETTY_FUNCTION__ << endl
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/6.3.0/../../../../include/c++/6.3.0/ostream:556:5: note: declared here
operator<<(basic_ostream<char, _Traits>& __out, const char* __s)
^
1 error generated.
$>
This seems like a bug with g++ because the constructor appears to violate the restrictions of constexpr
, but I am not quite sure. Which compiler is correct?
Here is the g++ version and here is the clang version (on ideone).
With constexpr constructors, objects of user-defined types can be included in valid constant expressions. The containing class must not have any virtual base classes. Each of the parameter types is a literal type. It is not a function try block.
Fair enough, constexpr functions can't contain any cout statements, as we know. But what happens if we do this? clang allows it! OK, but this can't possibly be a constexpr function even though it is marked constexpr, let's try to use it in constexpr context. It works! So it must be clang bug? Reason I say clang because gcc complains:
A constexpr function must satisfy the following requirements: for constructor and destructor (since C++20), the class must have no virtual base classes
C++ 14 allows more than one statement. constexpr function should refer only to constant global variables. constexpr function can call only other constexpr function not simple function. The function should not be of a void type and some operators like prefix increment (++v) are not allowed in constexpr function.
Both gcc and clang are correct, your program is ill-formed no diagnostic required since there is no way to invoke the constructors such that they could be evaluated as a sub-expression of a core constant expression.
From [dcl.constexpr]p5:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression ([expr.const]), the program is ill-formed; no diagnostic required. [ Example:
constexpr int f(bool b) { return b ? throw 0 : 0; } // OK constexpr int f() { return f(true); } // ill-formed, no diagnostic required struct B { constexpr B(int x) : i(0) { } // x is unused int i; }; int global; struct D : B { constexpr D() : B(global) { } // ill-formed, no diagnostic required // lvalue-to-rvalue conversion on non-constant global };
— end example ]
If we force the constructor to be evaluated in a constant expression context then you will receive a diagnostic from gcc as well (see it live):
{ constexpr A a; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With