Подтвердить что ты не робот

Перегрузка шаблона оператора преобразования

Рассмотрим следующий простой пример

struct C
{
    template <typename T> operator T () {return 0.5;}
    operator int () {return 1;}
    operator bool () {return false;}
};

int main ()
{
    C c;
    double x = c;
    std::cout << x << std::endl;
}

При компиляции с помощью Clang он дает следующую ошибку:

test.cpp:11:12: error: conversion from 'C' to 'double' is ambiguous
    double x = c;
           ^   ~
test.cpp:4:5: note: candidate function
    operator int () {return 1;}
    ^
test.cpp:5:5: note: candidate function
    operator bool () {return false;}
    ^
test.cpp:3:27: note: candidate function [with T = double]
    template <typename T> operator T () {return 0.5;}
                          ^
1 error generated.

Другие компиляторы генерируют подобные ошибки, например, GCC и Intel iclc

Если я удалю operator int и operator bool. Он отлично компилируется и работает, как ожидалось. Если удалить только один из них, то есть сохранить оператор шаблона и сказать operator int, тогда всегда выбирается версия без шаблона.

Я понимаю, что только когда перегруженные шаблоном и не шаблоном функции равны в том смысле, что они оба идеально подходят или оба требуют одной и той же последовательности конверсии, предпочтительной будет версия без шаблонов. Однако в этом случае кажется, что компилятор не видит шаблон оператора как идеальное совпадение. И когда присутствуют перегрузки bool и int, то, естественно, они считают, что они неоднозначны.

В заключение, мой вопрос заключается в том, почему шаблон оператора не считается идеальным совпадением в этом случае?

4b9b3361

Ответ 1

Это интересно. Существует два способа прочитать критическую часть раздела 13.3.3. Исходный пример должен обязательно вызвать шаблон функции, но версия, в которой удаляется один из не-шаблонов, может считаться неоднозначной.

13.3.3:

Жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2, если для всех аргументов я ICS_i (F1) не является худшей последовательностью преобразования, чем ICS_i (F2), и затем

  • для некоторого аргумента j, ICS_j (F1) является лучшей последовательностью преобразования, чем ICS_j (F2), или, если не это,

  • контекст представляет собой инициализацию путем пользовательского преобразования (см. 8.5, 13.3.1.5 и 13.3.1.6) и стандартную последовательность преобразования из возвращаемого типа F1 в тип назначения (т.е. тип инициализируемого объекта) является лучшей последовательностью преобразования, чем стандартная последовательность преобразования из возвращаемого типа F2 в тип назначения, или, если это не так,

  • F1 - функция без шаблона, а F2 - специализированная функция шаблона или, если не это,

  • F1 и F2 являются специализированными шаблонами функций, а шаблон функции для F1 более специализирован, чем шаблон для F2 в соответствии с правилами частичного упорядочения, описанными в 14.5.6.2.

Если существует только одна жизнеспособная функция, которая является лучшей функцией, чем все другие жизнеспособные функции, то она выбирается с помощью разрешения перегрузки; иначе звонок плохо сформирован.

В примере clang правильно идентифицирует набор из трех жизнеспособных функций-кандидатов:

C::operator int()
C::operator bool()
C::operator double<double>()

Третий - это специализированная функция. (Я не думаю, что синтаксис выше является законным, но вы получаете идею: в этот момент разрешения перегрузки он не рассматривается как шаблон, а как специализация с определенным типом функции.)

Единственная неявная последовательность преобразования в аргументах здесь (ICS1) - это точное соответствие "lvalue C" на "C&" для неявного параметра, поэтому это не будет иметь значения.

Этот пример - это точно ситуация, описанная во втором марке, поэтому функция, возвращающая double, явно лучше, чем две другие.

Здесь, где это становится странным: по буквальному чтению, operator int также лучше, чем специализация шаблона, из-за третьей пули. "Подождите, не лучше, чем" быть антисимметричным ". Как вы можете сказать F1 лучше, чем F2 И F2 лучше, чем F1?" К сожалению, Стандарт явно не говорит ничего подобного. "Разве вторая пуля не имеет приоритета над третьей пулей из-за фразы" если не эта "? Да, для констант F1 и F2. Но в Стандарте не говорится, что выполнение второй пули для (F1,F2) делает третью пулю для (F2,F1) неприменимой.

Конечно, поскольку operator int не лучше, чем operator bool и наоборот, существует еще "одна жизнеспособная функция, которая является лучшей функцией, чем все другие жизнеспособные функции".

Я не совсем одобряю это странное чтение, за исключением, возможно, сообщения об этом как о стандартном дефекте. Переход с этим имел бы странные последствия (например, удаление перегрузки, которая не была лучшей из этого примера, изменяет программу от хорошо сформированной до двусмысленной!). Я думаю, что цель состоит в том, чтобы вторая пуля была рассмотрена в обоих направлениях, прежде чем третья пуля будет рассмотрена вообще.

Что означало бы, что шаблон функции должен быть выбран с помощью разрешения перегрузки, и это ошибка clang.

Ответ 2

Разбейте это на две разные проблемы:

1. Почему это порождает ошибку компилятора?

struct C
{
    operator bool () {return false;}
    operator int () {return 1;}
};

Поскольку оба int и bool могут быть неявно преобразованы в double, компилятор не может знать, какую функцию он должен использовать. Есть две функции, которые он может использовать, и ни один из них не имеет приоритета над другим.

2. Почему идеальная версия шаблона не подходит?

struct C
{
    template <typename T> operator T () {return 0.5;}
    operator int () {return 1;}
};

Почему operator int() вызывается при запросе двойника?

Функция без шаблона вызывается потому, что функция не-шаблона имеет приоритет при разрешении перегрузки. (Перегрузка шаблонов функций)

EDIT: Я был неправ! Как отметил Ян Чжоу в своем комментарии, и, как указано в ссылке, которую я предоставил, идеальное совпадение в шаблонной функции имеет приоритет над нетемпликационной функцией.

Я проверил ваш код (скомпилированный с g++ 4.7.2), и он работал как ожидалось: он возвратил 0.5, другими словами, была использована шаблонная функция!

EDIT2: Я теперь пробовал с clang, и я могу воспроизвести описанное вами поведение. Поскольку он корректно работает в gcc, это кажется ошибкой в ​​clang.