3

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;
}
4
  • live demo godbolt.org/g/xw8rcr Commented Jul 27, 2017 at 11:15
  • 1
    Btw, why do you need a nested struct here? Commented Jul 27, 2017 at 11:33
  • @erenon because I don’t need to call the function anywhere else. And recursive lambdas aren’t a thing Commented Jul 27, 2017 at 11:45
  • 1
    For some reason the default constexpr constructor is not created. The same issue happens if you use a named classes instead of an anonymous struct and then declare a variable of that type. I guess that {} forces the compiler to generate the default constructor and call it. Commented Jul 27, 2017 at 12:15

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! There’s no way I would have figured that out. would you suggest reporting this to clang ?
@DanielCollier: I would. I've admittedly never submitted a bug report to them (despite encountering plenty over time), so I'm unfamiliar with the process.
1

Just move it out as another function:

constexpr double sqrtNewtonRaphson (double x, double current, double previous) {
            return current == previous ? current : sqrtNewtonRaphson(x, 0.5 * (current + x / current), current);
        }
constexpr double compile_time_sqrt_ver2(double x) {
    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;
}

EDIT made method static

constexpr double compile_time_sqrt_ver2(double x) {
    struct SNR{
        static constexpr double sqrtNewtonRaphson (double x, double current, double previous) {
            return current == previous ? current : sqrtNewtonRaphson(x, 0.5 * (current + x / current), current);
        }
    }; 

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

3 Comments

I am aware that I can do this, but i want to know what the issue is whith Gcc not accepting it but clang does. I also don’t want to make another function as it will not be used anything else other than the compile_time_sqrt function. Using a lambs would be great but I don’t think it’s possible
Lamba* I’m not an animal
If you want to keep it in the struct you don't actually need an instance. see edit

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.