Перегрузка оператора C++ и связанное пространство имен
Следующий упрощенный пример компилируется в gcc
а также Visual Studio
, но не в clang
!?
namespace N
{
struct A {};
template <typename T>
double operator+ (T a, double d) {return d;}
template <typename T>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
На мой взгляд, шаблонный operator+
в пространстве имен N
должен быть найден ADL.
Почему clang
не согласен? Это ошибка в clang
или в других компиляторах?
Вот ошибка компиляции из clang 3.5.1 (проверено на coliru), я не понимаю, в чем здесь проблема...
10 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (double d, T a) {return d;}
^
18 : note: in instantiation of function template specialization 'N::operator+' requested here
double y = a + x;
^
7 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (T a, double d) {return d;}
^
19 : note: in instantiation of function template specialization 'N::operator+' requested here
double z = x + a;
^
2 errors generated.
Compilation failed
Конечно, этот пример упрощен из реального кода. Предполагается, что любой класс, определенный внутри пространства имен N, имеет перегруженный оператор + с двойным.
2 ответа
Это вызвано двумя разными проблемами CWG: проблема CWG 2052 и проблема CWG 1391.
Сначала CWG 1391. При встрече x + a
обычный поиск имени находит, среди других перегрузок,
template <typename T> double operator+ (T, double);
Вывод аргумента шаблона выполняется по совпадению T
к типу lhs +
, который double
так это выводит T
быть double
, Тип второго параметра не содержит параметра шаблона, поэтому не учитывается в текущих правилах. Чтобы быть уверенным, N::A
не может быть преобразован в double
поэтому полученная специализация нежизнеспособна, но текущие правила говорят, что вывод аргументов шаблона не заботится об этом; это будет обработано в разрешении перегрузки.
Предлагаемая резолюция для CWG 1391, помимо прочего, добавляет новый стандарт к стандарту:
Если вывод выполняется успешно для всех параметров, которые содержат параметры шаблона, которые участвуют в выводе аргументов шаблона, и все аргументы шаблона явно указываются, выводятся или получаются из аргументов шаблона по умолчанию, оставшиеся параметры затем сравниваются с соответствующими аргументами. Для каждого оставшегося параметра
P
с типом, который не зависел перед заменой каких-либо явно заданных аргументов шаблона, если соответствующий аргумент А не может быть неявно преобразован вP
, вычет не удается. [ Примечание: параметры с зависимыми типами, в которых нет шаблонных параметров, участвующих в выводе аргументов шаблона, и параметры, которые стали независимыми из-за замены явно заданных аргументов шаблона, будут проверяться во время разрешения перегрузки. - конец примечания ]
Другими словами, если аргумент (a
в нашем случае) соответствует независимому параметру (double
) не может быть преобразовано в тип параметра, вычет просто потерпит неудачу. Таким образом, в нашем случае вывод из аргумента шаблона после CWG1391 завершится с ошибкой, и все будет хорошо.
Тем не менее, Clang реализует текущие правила T = double
происходит замена, и мы сталкиваемся с CWG 2052. Цитируем рецензию Ричарда Смита (разработчика Clang):
В примере как
struct A { operator int(); }; template<typename T> T operator<<(T, int); void f(A a) { 1 << a; }
Вывод аргумента шаблона успешно выполняется для шаблона оператора, создавая подпись
operator<<(int,int)
, Результирующее объявление синтезируется и добавляется к набору перегрузки в соответствии с пунктом 14.8.3 [temp.over]. Однако это нарушает требование пункта 13 13 [over.oper],Операторная функция должна быть либо нестатической функцией-членом, либо функцией, не являющейся членом, которая имеет по крайней мере один параметр, тип которого является классом, ссылкой на класс, перечислением или ссылкой на перечисление.
Это не контекст SFINAE, поэтому программа имеет неправильную структуру, а не выбирает встроенный оператор.
В этом случае нет преобразования, поэтому вывод operator+(double, double)
на самом деле не является жизнеспособным, но нежизнеспособные кандидаты не удаляются, пока вы не создадите набор кандидатов, и здесь создание набора кандидатов вызывает серьезную ошибку.
Предложенное разрешение для CWG 2052 сделает этот случай SFINAE, а также заставит работать оригинальный код. Проблема в том, что Clang также реализует текущую версию стандарта.
Это может быть жаловаться, потому что T
неможет быть классом в этом определении. И вы не можете переопределить стандарт operator+
для арифметических типов IIRC. В вашем примере нет ничего ограничивающего T
быть N::A
например.
Добавление typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>
кажется, это исправить. Visual Studio и GCC могут быть немного более ленивыми в отношении этого ограничения.
namespace N
{
struct A {};
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (T a, double d) {return d;}
template <typename T, typename = std::enable_if_t<std::is_class<T>{} || std::is_enum<T>{}>>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}