Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this code valid? Works with gcc, don't work with clang [duplicate]

The following minimal code compiles on g++, but won't compile on clang++:

template<class T>
T operator*(float a, const T& b)
{
    return b * a;
}

struct A{
    A operator*(float b) const
    {
        A a;
        return a;
    }
};

int main()
{
    A a;
    2.0f * a;
}

This is the error I get:

$ clang++ test.cpp 
test.cpp:2:3: error: overloaded 'operator*' must have at least one parameter of
      class or enumeration type
T operator*(float a, const T& b)
  ^
test.cpp:4:11: note: in instantiation of function template specialization
      'operator*<float>' requested here
        return b * a;
                 ^
test.cpp:18:10: note: in instantiation of function template specialization
      'operator*<A>' requested here
    2.0f * a;
         ^
1 error generated.

Clang version 3.5. Is this code valid? Is there a bug on Clang?

like image 246
lvella Avatar asked Oct 25 '14 19:10

lvella


1 Answers

2.0f * a; instantiates ::operator*<A>. Within that function, we have the expression b * a, which, if you look at the (simplified) types, is A * float. At this point, the compiler needs to make a choice. Should that * be the global function ::operator*<float> (because the right hand argument is float), or should it be A::operator*? To us humans, it's clear it should be A::operator*, but from the compiler's perspective it's not immediately clear.

So what does the compiler do? It first tries to find all the operator* functions that could be used (after which, it tries to determine exactly which one to use). One of those operator* functions that could be used is ::operator*<float>. But wait, what is ::operator*<float>? It's float *(float, const float&)! And we can't do that! You can't overload operators for primitive types (imagine the chaos if you overloaded int +(int, int) so you make 1 + 2 do something totally different from what everyone expected it to do).

At this point, the program is ill-formed. The mere fact that the compiler even tries to instantiate ::operator*<float> invalidates the program as a whole. So what can we do? Tell the compiler exactly what to do:

template<class T>
T operator*(float a, const T& b)
{
    // This prevents the compiler from instantiating ::operator*<float>
    return b.operator*(a);

    // The above is meant to illustrate how the fix needs to work: it needs
    // to avoid instantiating ::operator*<float>. Other methods can be used
    // (like SFINAE) that might be more elegant (check out Walter's answer
    // in the duplicate: https://stackoverflow.com/a/18596809/1287251), but
    // in the end any solution used must avoid ::operator*<float>.
}

struct A{
    A operator*(float b) const
    {
        A a;
        return a;
    }
};

int main()
{
    A a;
    2.0f * a;
}

In short, to answer the question: no, the code is not valid. You must prevent the compiler from trying to instantiate ::operator*<float>.

This is explained by @dyp in the comments and by @TemplateRex in the duplicate question. However, I had to read their responses several times before I understood what they meant. I've tried to simplify things in this answer. If I can improve it, please let me know!

like image 170
Cornstalks Avatar answered Sep 19 '22 16:09

Cornstalks