Странная ошибка с перегрузкой шаблонного оператора

Когда я компилирую следующий фрагмент, я получаю ошибку компилятора с clang, но не с g++/MSVC:

#include <string>

template<typename T> struct Const { 
    explicit Const(T val) : value(val) {}
    T value;
};

template<typename T> struct Var {
    explicit Var(const std::string &n) : name(n) {}

    std::string name;
};

template<typename L, typename R> struct Greater {
    Greater(L lhs, R rhs) : left(lhs), right(rhs) {}

    L left;
    R right;
};

template<typename L>
Greater<L, Const<int> > operator > (L lhs, int rhs) { 
    return Greater<L, Const<int> >(lhs, Const<int>(rhs));
}

template<typename R>
Greater<Const<int>, R> operator > (int lhs, R rhs) { 
    return Greater<Const<int>, R>(Const<int>(lhs), rhs);
}

Var<double> d("d");

int main() {
     d > 10;
     return 0;
}

Сообщается об ошибке следующего содержания:

error: overloaded 'operator>' must have at least one parameter of
      class or enumeration type
Greater<Const<int>, R> operator > (int lhs, R rhs) { 
                       ^
./val.h:31:24: note: in instantiation of function template specialization
      'operator><int>' requested here
Greater<Const<int>, R> operator > (int lhs, R rhs) { 
                       ^
1 error generated.

что касается функции оператора, которая не используется. Если вместо этого я напишу 10 > d вместо d > 10, то я получу ту же ошибку о другой операторной > функции. Вышеприведенное отлично компилируется под gcc 4.4.6 и VS2012. В чем моя ошибка?

Спасибо.


person John    schedule 03.09.2013    source источник
comment
Он также отлично компилируется с gcc 4.8.1: ideone.com/wG4Yzv (режим C++98) и ideone.com/8fwOWq (режим C++11). Какая у тебя версия лязга?   -  person gx_    schedule 03.09.2013
comment
@gx_ Только что попробовал 3.1 и 3.2, в обоих есть проблема.   -  person BoBTFish    schedule 03.09.2013
comment
@BoBTFish Спасибо (жаль, что Clang больше нет в сети). Меня интересует очень упрощенный пример ideone.com/0Aooxo? Редактировать: о, подождите, есть clang 3.0 на gcc.godbolt.org :) и мой упрощенный пример вызывает ту же ошибку. Edit(2): Еще раз спасибо. Похоже на ошибку компилятора...   -  person gx_    schedule 03.09.2013
comment
Ваш упрощенный пример демонстрирует ту же ошибку. И удаление неправильного приводит к успеху.   -  person BoBTFish    schedule 03.09.2013
comment
Или, может быть, на самом деле это не ошибка в Clang, но другие компиляторы слишком разрешительны :p Кстати, я где-то читал, что таких неограниченных шаблонов лучше избегать (особенно для перегрузок операторов)... Найдено: Скромное предложение: исправление ADL (редакция 2)   -  person gx_    schedule 03.09.2013
comment
@gx_ Моя версия clang — версия 3.2 (на основе llvm 3.2). Это стандартный пакет clang для Ubuntu 13.04.   -  person John    schedule 03.09.2013


Ответы (3)


Clang прав: для перегрузки оператора требуется хотя бы один параметр типа класса или перечисления, иначе программа будет некорректной (13.5/1). Чтобы понять, почему эта ошибка вообще появляется, нам нужно еще немного разобрать стандартный юридический язык.

Вспомните Святую Троицу поиска имен, вывода аргументов и разрешения перегрузки. Первый шаг находит два перегруженных файла operator>. Второй шаг выводит аргументы шаблона для каждой версии. Вы можете подумать, что вторая перегрузка станет жертвой правила SFINAE (14.8.2), так что до третьего шага доживет только первая. Однако это не сбой подстановки (как, например, отсутствие вложенного определения типа), а недопустимая конструкция (см. ранее упомянутый 13.5/1). Это само по себе делает программу неправильной (14.3/6)

6 Если использование шаблона-аргумента приводит к неправильно сформированной конструкции при реализации специализации шаблона, программа является неправильно сформированной.

В 14.8.3 упоминается, что эта проверка выведенных аргументов происходит до разрешения перегрузки, поэтому предпочитаемый вами оператор не может быть выбран.

В качестве обходного пути С++ 03 вы можете определить двух друзей, не являющихся шаблонами operator>, внутри вашего шаблона класса Var<T>. Они будут внедрены в окружающее (в данном примере глобальное) пространство имен как функции, не являющиеся шаблонами, с одним параметром типа класса, поэтому приведенная выше ошибка не должна возникать.

person TemplateRex    schedule 03.09.2013
comment
В вопросе ОП в качестве примера используется operator >, но в вашем ответе говорится о operator <, это просто опечатка? - person greatwolf; 26.10.2014
comment
DR2052 и DR1391 теперь делает Clang неверным двумя разными способами, но похоже, их решения еще не реализованы. Также имеет значение ошибка Clang 13869. - person bogdan; 25.07.2016

Должен признаться, я действительно не знаю, почему здесь жалуется clang, это похоже на ошибку (компилятора). Кстати, clang 3.3 также демонстрирует проблему.

Вы можете подавить его с помощью SFINAE:

template<typename L>
typename std::enable_if<std::is_class<L>::value || std::is_enum<L>::value,
                        Greater<L, Const<int>>>::type
operator > (L lhs, int rhs) { 
    return Greater<L, Const<int> >(lhs, Const<int>(rhs));
}

template<typename R>
typename std::enable_if<std::is_class<R>::value || std::is_enum<R>::value,
                        Greater<Const<int>,R>>::type
operator > (int lhs, R rhs) { 
    return Greater<Const<int>, R>(Const<int>(lhs), rhs);
}
person Walter    schedule 03.09.2013
comment
К сожалению, я могу использовать только C++03. Исправьте, если я ошибаюсь, но std::enable_if, std::is_class и т. д. являются конструкциями С++ 11, поэтому я не могу их использовать. - person John; 03.09.2013
comment
@John enable_if — это несколько строк кода, которые вы можете написать сами, но is_class требует поддержки компилятора, поэтому вы не можете использовать его без С++ 11. - person TemplateRex; 04.09.2013

Для меня это выглядит как ошибка в g++ и VS. В вашем примере ваш тип R равен int (поскольку правый операнд равен int). Затем это делает сигнатуру функции Greater<Const<int>, R> operator > (int lhs, int rhs) которая является той же (параметрической) сигнатурой, что и встроенная operator< для целых чисел. Обратите внимание, что он должен учитывать оба шаблона (и пытаться вывести типы отдельно для каждого), решая, какой operator> использовать: он не может просто посмотреть на один из них и решить игнорировать другой.

person Mark B    schedule 03.09.2013
comment
При рассмотрении обоих шаблонов недопустимое создание экземпляра шаблона следует игнорировать или это ошибка? - person John; 03.09.2013
comment
@John нет, только ошибки замены не являются ошибками (sfinae), любая другая недопустимая конструкция во время вывода аргумента является серьезной ошибкой. Смотрите мой ответ для более подробной информации. - person TemplateRex; 04.09.2013