Search code examples
c++language-lawyerc++20compiler-bugnon-type-template-parameter

Clang fails to instantiate `operator!=()` from `operator==()` with `auto` return type for a class object passed through a non-type template parameter


Consider the following code:

template <typename T>
struct Wrapper {
    T value;
    constexpr auto operator==(const T other) const {
        return value == other;
    }
};

template <Wrapper W>
void fun() {
    static_assert(W != 0); // This line fails on Clang
}

int main() {
    fun<Wrapper{1}>();
}

The above compiles successfully on GCC but fails to compile on Clang with the following error message:

<source>:11:21: error: return type 'auto' of selected 'operator==' function for rewritten '!=' comparison is not 'bool'
   11 |     static_assert(W != 0); // This line fails on Clang
      |                   ~ ^  ~
<source>:15:5: note: in instantiation of function template specialization 'fun<Wrapper<int>{1}>' requested here
   15 |     fun<Wrapper{1}>();
      |     ^
<source>:4:20: note: declared here
    4 |     constexpr auto operator==(const T other) const {
      |                    ^

Note that using bool instead of auto makes the program compile on both compilers.


However, interestingly enough, when I change the definition of fun() to this:

template <Wrapper W>
void fun() {
    void(W == 0); // Adding this line makes the error go away
    static_assert(W != 0);
}

It compiles fine on both compilers.

Alternatively, if I change the definition of the class Wrapper to not use a template parameter, it yet again successfully compiles on both compilers.


I believe this might a Clang/LLVM bug but I'm not sure if that's really the case.


Solution

  • This is a known clang bug (LLVM Issue 76649).

    Firstly, note that the process of placeholder type deduction is described in Clause 9 of the standard, and it isn't said when that process actually takes place; that is up to the compiler. However, it is said to "replace" the placeholder type, and we we know that this has to happen prior to name lookup:

    If a variable or function with an undeduced placeholder type is named by an expression ([basic.def.odr]), the program is ill-formed.

    - [dcl.spec.auto.general] p11

    Therefore, even with placeholder return types, the requirement in [over.match.oper] p10 should be satisfied:

    If a rewritten operator== candidate is selected by overload resolution for an operator @, its return type shall be cv bool

    At the point where this is verified, the placeholder type should have been replaced with bool. Therefore, it's a clang bug that the selected operator== is not considered to have a bool return type.

    As for:

    void(W == 0); // Adding this line makes the error go away
    static_assert(W != 0);
    

    Clang correctly accepts this. I suspect that this works because the return type has been replaced by bool once W == 0 has run, and the subsequent W != 0 than piggybacks off of this. Perhaps placeholder type deduction is somehow delayed or inhibited inside of unevaluated operands, which makes it fail when != is used in static_assert first.