При разрешении перегрузки выбор функции, которая использует неоднозначную последовательность преобразования, обязательно приводит к неправильному формированию вызова?
Вопрос возник, когда я искал ответ на этот вопрос. Рассмотрим следующий код:
struct A{
operator char() const{ return 'a'; }
operator int() const{ return 10; }
};
struct B {
void operator<< (int) { }
};
int main()
{
A a;
B b;
b << a;
}
Преобразование a
в int
может быть либо через a.operator char()
с последующим интегральным продвижением или a.operator int()
с последующим преобразованием личности (т. е. вообще без преобразования). Стандарт гласит, что (§13.3.3.1 [over.best.ics]/p10, сноска опущена, выделена жирным шрифтом; все цитаты взяты из N3936):
Если существует несколько различных последовательностей преобразований, каждая из которых преобразует аргумент в тип параметра, последовательность неявного преобразования, связанная с параметром, определяется как уникальная последовательность преобразования, обозначенная неоднозначной последовательностью преобразования. В целях ранжирования последовательностей неявного преобразования, как описано в 13.3.3.2, неоднозначная последовательность преобразования обрабатывается как определенная пользователем последовательность, которая неотличима от любой другой определенной пользователем последовательности преобразования. Если в качестве наилучшей жизнеспособной функции выбрана функция, использующая последовательность неоднозначного преобразования, вызов будет некорректным, поскольку преобразование одного из аргументов в вызове является неоднозначным.
Вот, B::operator<<(int)
является единственным жизнеспособным кандидатом - и, следовательно, является лучшим жизнеспособным кандидатом, даже если последовательность преобразования для параметра является неоднозначной последовательностью преобразования. В соответствии с жирным предложением, вызов должен быть некорректным, потому что "преобразование одного из аргументов в вызове неоднозначно".
Тем не менее ни один из протестированных мной компиляторов (g++, clang и MSVC) на самом деле не сообщает об ошибке, что имеет смысл, потому что после выбора вызываемой функции с помощью разрешения перегрузки параметр "функции" (8.3.5) должен быть инициализирован (8.5, 12.8, 12.1) с соответствующим аргументом " (§5.2.2 [expr.call]/p4). Эта инициализация является копией-инициализацией (§8.5 [dcl.init]/p15) и, согласно §8.5 [dcl.init]/p17, приводит к новому раунду разрешения перегрузки для определения функции преобразования для использования:
Семантика инициализаторов следующая. Тип назначения - это тип инициализируемого объекта или ссылки, а тип источника - тип выражения инициализатора. Если инициализатор не является одним (возможно, заключенным в скобки) выражением, тип источника не определен.
- [...]
- Если тип назначения является (возможно, cv-квалифицированным) типом класса: [...]
- В противном случае, если тип источника является (возможно, cv-квалифицированным) типом класса, рассматриваются функции преобразования. Перечислены применимые функции преобразования (13.3.1.5), и лучшая из них выбирается с помощью разрешения перегрузки (13.3). Выбранное пользователем преобразование, выбранное таким образом, вызывается для преобразования выражения инициализатора в инициализируемый объект. Если преобразование не может быть выполнено или является неоднозначным, инициализация неверна.
- [...]
И в этом раунде разрешения перегрузки есть разделитель связей в §13.3.3 [over.match.best]/p1:
жизнеспособная функция
F1
определяется как лучшая функция, чем другая жизнеспособная функция F2, если для всех аргументовi
,ICSi(F1)
не хуже последовательности преобразования, чемICSi(F2)
, а потом
- для некоторого аргумента
j
,ICSj(F1)
является лучшей последовательностью преобразования, чемICSj(F2)
или, если не так,- контекст представляет собой инициализацию с помощью пользовательского преобразования (см. 8.5, 13.3.1.5 и 13.3.1.6) и стандартной последовательности преобразования из возвращаемого типа
F1
для типа назначения (т. е. тип инициализируемого объекта) является лучшей последовательностью преобразования, чем стандартная последовательность преобразования из возвращаемого типаF2
к типу назначения.(Пример и остальная часть списка опущены)
Поскольку стандартная последовательность преобразования из int
в int
(Точное совпадение ранга) лучше, чем стандартная последовательность преобразования из char
в int
(Повышение звания), первое бьет второе, и не должно быть никакой двусмысленности - преобразование определяется operator int()
будет использоваться для инициализации, что затем противоречит предложению в §13.3.3.1 [over.best.ics] / p10, в котором говорится, что вызов функции будет некорректным из-за неоднозначности.
Что-то не так в приведенном выше анализе или это предложение является ошибкой в стандарте?
1 ответ
При выборе наилучшего пользовательского преобразования для пользовательской последовательности преобразования мы имеем набор кандидатов на перегрузку.
§13.3.3 / p1 говорит:
Определите ICSi(F) следующим образом:
[...]
пусть ICSi(F) обозначает последовательность неявного преобразования, которая преобразует i-й аргумент в списке в тип i-го параметра жизнеспособной функции F. 13.3.3.1 определяет последовательности неявного преобразования, а 13.3.3.2 определяет, что это означает для одной неявной последовательности преобразования быть лучшей последовательностью преобразования или худшей последовательностью преобразования, чем другой.
С учетом этих определений жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2, если для всех аргументов i ICSi(F1) не хуже последовательности преобразования, чем ICSi(F2), а затем
- [...]
- контекст представляет собой инициализацию путем пользовательского преобразования (см. 8.5, 13.3.1.5 и 13.3.1.6) и стандартной последовательности преобразования из возвращаемого типа F1 в тип назначения (т. Е. Тип инициализируемого объекта) является лучшей последовательностью преобразования, чем стандартная последовательность преобразования из типа возврата F2 в тип назначения.
Это относится с
§13.3.3.1.2 / p2
2 Вторая стандартная последовательность преобразования преобразует результат пользовательского преобразования в целевой тип последовательности. Поскольку неявная последовательность преобразования является инициализацией, специальные правила для инициализации путем пользовательского преобразования применяются при выборе лучшего пользовательского преобразования для пользовательской последовательности преобразования (см. 13.3.3 и 13.3.3.1).
и, следовательно, последовательность преобразования с участием operator int
выбран в качестве лучшего соответствия.
Наконец, я бы перефразировал §13.3.3.1 / p10 как
Если существует несколько различных последовательностей преобразований, каждая из которых преобразует аргумент в тип параметра, и было невозможно определить наилучшего кандидата, последовательность неявного преобразования, связанная с параметром, определяется как уникальная последовательность преобразования, обозначенная неоднозначной последовательностью преобразования. В целях ранжирования последовательностей неявного преобразования, как описано в 13.3.3.2, неоднозначная последовательность преобразования обрабатывается как определенная пользователем последовательность, которая неотличима от любой другой определенной пользователем последовательности преобразования134. Если в качестве наилучшей жизнеспособной функции выбрана функция, использующая последовательность неоднозначного преобразования, вызов будет некорректным, поскольку преобразование одного из аргументов в вызове является неоднозначным.