简体   繁体   中英

Inconsistent overload resolution for constexpr member functions across compilers

I ran into a compiler error that only reproduces on gcc, I narrowed it down to a minimal reproducible sample that also fails on msvc but still compiles fine with clang. Here's the code:

struct vec
    {
        float _x[2];

        constexpr float operator[](int index) const { return _x[index]; }
        float& operator[](int index) { return _x[index]; }
    };

    struct mat
    {
        vec _x[2];

        constexpr vec operator[](int index) const { return _x[index]; }
        vec& operator[](int index) { return _x[index]; }
    };

    constexpr float bar(float f)
    {
        return f;
    }

    constexpr float one(mat const& m)
    {
        return m[0][0]; // fails in gcc 5+, msvc
    }

    constexpr float two(mat const& m)
    {
        return bar(m[0][0]); // fails in gcc 5+
    }

From what I can tell, overload resolution for vec::operator[] on line 24 does not consider the const overload (line 5) because mat::operator[] const (line 13) returns by value, not by const reference, but I'm not sure why that prevents consideration of vec::operator[] const. Error message from gcc:

: In function 'constexpr float one(const mat&)':
    :24:18: error: call to non-constexpr function 'float& vec::operator[](int)'
        return m[0][0]; // fails in gcc 5+, msvc

And from msvc:

(22): error C3615: constexpr function 'one' cannot result in a constant expression
    (24): note: failure was caused by call of undefined function or one not declared 'constexpr'
    (24): note: see usage of 'vec::operator []'

The original code compiles fine in msvc but the sample does not so it took me a little bit to find what was allowing it work with msvc. Apprently, passing the return value through another constexpr function somehow forces msvc to consider the const overload but I have no idea what the reason for this is. Is this a bug or the result of some esoteric language rules? Which compiler is correct?

Last question here is that this is only a problem because the const overloads return by value, if they return by const reference there are no errors on any compiler. Is returning by value here a useless pessimization that I should remove?

Which compiler is correct?

In this case, both compilers are correct (or at least, not incorrect). The C++ Standard says ( [dcl.constexpr]/5 ) that any constexpr function which cannot possibly be invoked in a constant expression makes the program "ill formed; no diagnostic required". This means a program with that condition is incorrect, but implementations (compilers) are not required to print any diagnostic message about it.

You figured correctly that the expression m[0][0] first calls constexpr vec mat::operator[](int) const; and then calls float& vec::operator[](int); , and the problem is that this vec::operator[] is not a constexpr function.

but I'm not sure why that prevents consideration of vec::operator[] const .

Note that overload resolution for any function call, including an overloadable operator, uses only the types and value categories of the subexpressions, including the implicit class-type argument in the case of a non-static member function. In particular here, it does not consider whether the expression is being used within a constant expression or within an always-executed piece of a constexpr function. It would just be too confusing if two expressions spelled with the exact same code involved calling entirely different functions depending on how that expression is used.

In this case, m[0][0] is entirely valid when not used in a constant expression:

float runtime_func(const mat& m) { return m[0][0]; }

and in that use, it calls constexpr vec mat::operator[](int) const; since the type of m is const-qualified, and then float& vec::operator[](int); since the type of m[0] is vec , not const-qualified. So one and two call exactly the same functions, and both are ill-formed with no diagnostic required.

I can't say why MSVC gives an error for function one but not for function two . But I don't think it's actually using float vec::operator[](int) const; instead. I notice if I actually try to use two within a constant expression, as in

constexpr mat m = {{{{1,2}}, {{3,4}}}};
constexpr float val = two(m);

then MSVC does give somewhat helpful error messages at that point. That MSVC missed the opportunity to flag the issue earlier can be considered a quality of implementation problem.

Is returning by value here a useless pessimization that I should remove?

In the code shown, there's no real reason to not just make all four operator[] functions constexpr . Objects that are not const can be used in a constant expression, as long as their initialization happened during that same constant expression, and this could apply to the temporary vec object involved here. (Of course, there might be some other things to consider in the more complete project where you found an issue before simplifying it for this question.)

Last question here is that this is only a problem because the const overloads return by value, if they return by const reference there are no errors on any compiler. Is returning by value here a useless pessimization that I should remove?

This question begins with a false assumption. This is not "only a problem because the const overloads return by value". Rather, the problem comes from the const overload of mat::operator[] returning something non-const , which leads to the compiler appropriately applying the non-const overload of vec::operator[] .

If you were to change the the const overload of mat::operator[] to the following, it would still return by value, but gcc's warning disappears.

constexpr const vec operator[](int index) const { return _x[index]; }

This is one of the cases where " constexpr const " is not redundant. The " constexpr " qualifies to the function, while the " const " qualifies the returned type.

On the other hand, if you are willing to return by reference in the non-const version, why not return by reference in the const version? Someone can already get a reference, so what is there to gain by throwing the inefficiency of a copy into the const version? (I might expect to see this the other way around: return by value for non-const so the member is not modified, but return by const reference in the const version for efficiency.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM