DanielCollier DanielCollier - 2 years ago 102
C++ Question

Nested struct inside constexpr function, compiles in clang, fails in gcc

Im having trouble with the following code, I am trying to write a compile time square root function. The code compiles on the most recent clang 6.0.
yet fails on the most recent version of gcc 8.0. the issue seems to be with the initialisation of the struct.

GCC output

error: uninitialized variable 'sqrtNewtonRaphson' in 'constexpr' context
} sqrtNewtonRaphson;
^~~~~~~~~~~~~~~~~


The last version of gcc that this code compiles on is gcc 6.3, in the following versions after that, compile_time_sqrt_ver1(double) fails to compile.

//------------------------------------------------------------
constexpr double
compile_time_sqrt_ver1(double x) {
struct {
constexpr double operator() (double x, double current, double previous) {
return current == previous ? current : (*this)(x, 0.5 * (current + x / current), current);
}
} sqrtNewtonRaphson;

return x >= 0 && x < std::numeric_limits<double>::infinity()
? sqrtNewtonRaphson(x, x, 0)
: std::numeric_limits<double>::quiet_NaN();
}

//------------------------------------------------------------
int main() {
constexpr double test_v1 = compile_time_sqrt_ver1(24);
return test_v1;
}


The solution I found to this is to add {} at the end of the struct making it compile in the most recent version of gcc as well as clang. why is this?

//------------------------------------------------------------
constexpr double
compile_time_sqrt_ver2(double x) {
struct {
constexpr double operator() (double x, double current, double previous) {
return current == previous ? current : (*this)(x, 0.5 * (current + x / current), current);
}
} sqrtNewtonRaphson{}; // <- change {}

return x >= 0 && x < std::numeric_limits<double>::infinity()
? sqrtNewtonRaphson(x, x, 0)
: std::numeric_limits<double>::quiet_NaN();
}

//------------------------------------------------------------
int main() {
constexpr double test_v2 = compile_time_sqrt_ver2(24);
return test_v2;
}

Answer Source

GCC is right; Clang is wrong.

Per [basic.types] your anonymous struct is an aggregate type (and therefore a literal type), so it should be constexpr constructible. (N4659 §6.9/10)

However, you are not initializing your aggregate. Aggregates are not default constructed upon declaration. This is why adding the braces {} afterwards enables it to work (aggregate initialization)

A constexpr function requires that all variables be initialized per [dcl.constexpr] §10.1.5/3.4.5 (emphasis mine)

The definition of a constexpr function shall satisfy the following requirements:
[...]
— its function-body shall be = delete, = default, or a compound-statement that does not contain
      — a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.

If you add a constexpr constructor, then it will also work (the type remains literal but is no longer aggregate, so you are not required to explicitly initialize it). However, this would also require you to name your struct.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download