Действительно ли этот код действителен? Работает с gcc, не работает с clang

Следующий минимальный код компилируется на g++, но не компилируется на 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;
}

Это ошибка, которую я получаю:

$ 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 версия 3.5. Действительно ли этот код действителен? Есть ли ошибка в Clang?

Ответ 1

2.0f * a; создает экземпляр ::operator*<A>. Внутри этой функции мы имеем выражение b * a, которое, если вы посмотрите на (упрощенные) типы, это A * float. На данный момент компилятор должен сделать выбор. Если * - глобальная функция ::operator*<float> (поскольку аргумент правой руки float) или должен быть A::operator*? Для нас, людей, ясно, что это должно быть A::operator*, но с точки зрения компилятора это не сразу понятно.

Так что же делает компилятор? Сначала он пытается найти все функции operator*, которые могут быть использованы (после чего он пытается точно определить, какой из них использовать). Одной из тех функций operator*, которые могут быть использованы, является ::operator*<float>. Но подождите, что такое ::operator*<float>? Это float *(float, const float&)! И мы не можем этого сделать! Вы не можете перегружать операторов для примитивных типов (представьте хаос, если вы перегрузили int +(int, int), чтобы сделать 1 + 2 сделать что-то совершенно отличное от того, что все ожидали от него).

В этот момент программа плохо сформирована. Сам факт, что компилятор даже пытается создать экземпляр ::operator*<float>, делает недействительным программу в целом. Так что мы можем сделать? Скажите компилятору, что именно делать:

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 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;
}

Короче говоря, для ответа на вопрос: нет, код недействителен. Вы должны запретить компилятору пытаться создать экземпляр ::operator*<float>.

Это объясняется @dyp в комментариях и by @TemplateRex в дублированном вопросе. Тем не менее, я должен был прочитать их ответы несколько раз, прежде чем я понял, что они имели в виду. Я попытался упростить ситуацию в этом ответе. Если я смогу его улучшить, сообщите мне!