Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr constructor gives a different result when evaluated at compile time by GCC

The constructor uses a function taking a reference and returning by value while repeatedly modifying a data member:

constexpr int   vv(int   x) {return x;}
constexpr int & rr(int & x) {return x;}
constexpr int   rv(int & x) {return x;}

constexpr struct S {
    int x {0};
    template<typename F> constexpr S(F f) {x = f(x) + 1; x = f(x) + 1;}
} s(rv); // s.x is 1 if function rv is used, 2 otherwise.
static_assert(s.x == 2, "");

It is only the function rv which gives the unexpected result when used in the constructor. If vv or rr is passed instead then s.x is 2 as expected.

I noticed the behavior on GCC 5.4.1 ARM and it appears to be the same in all versions of GCC that support C++14. Clang gives the expected result of 2 in all cases. Neither GCC nor Clang give any warnings with Wall and Wextra enabled.

Is this example valid C++14 and a bug in GCC? I read the list of restrictions on constant expressions in the standard and see nothing obvious it violates but I'm not sure I understand all the technical details.

like image 633
Hans Scholze Avatar asked Feb 14 '17 18:02

Hans Scholze


1 Answers

Your code is certainly intended to be well-formed. I believe GCC performs a revised form of memoization; back in C++11, objects could not be modified in constant expressions, hence it was perfectly fine to cache function results. In fact, it's quite apparent from a few Clang bug reports that GCC did exactly that, see e.g. here:

Clang's constexpr implementation does not perform caching. In C++14, it's not even clear whether caching will be possible, so it seems like a waste of time to invest in it now.

They obviously had to introduce some additional analysis in C++14, and they made a mistake: they assumed that any constexpr object's subobject cannot be modified during its lifetime. That clearly doesn't hold during the period of construction of the enclosing object. If we use a temporary instead of x, the code compiles. If we put the whole thing into a constexpr function and remove s's constexpr specifier, it works.

Funnily enough, rr's and vv's results are also cached, but the same reference being returned works fine, and call by value avoids that problem altogether :)

Reported as 79520.

like image 166
Columbo Avatar answered Oct 22 '22 18:10

Columbo