5

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

callyalater
  • 3,102
  • 8
  • 20
  • 27
  • 1
    Maybe related to [this one](https://stackoverflow.com/q/22176777/1708801) did you try `-fno-builtin` ... `operator <<` is not constexpr but it might be folded into a builtin. – Shafik Yaghmour Feb 22 '18 at 20:53
  • `-fno-builtin` doesn't affect either one. Regarding the other question, I wasn't sure if the relaxed requirements for `constexpr` in c++14 and c++17 affected the accepted answer. – callyalater Feb 22 '18 at 20:56
  • 2
    They are both correct ... my answer I linked to above explains why this is ill-formed no diagnostic required ... you are not forcing them to be used in a constant expression context [see modified version where gcc generates an error](https://wandbox.org/permlink/YOZPGIg4bdDcT525) when you force it to be used in constant expression context. – Shafik Yaghmour Feb 22 '18 at 21:02
  • 1
    If you want a C++14 specific quote see [this question](https://stackoverflow.com/q/34280729/1708801) which covers similar ground and gives the same details for C++14. – Shafik Yaghmour Feb 22 '18 at 21:07

1 Answers1

6

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