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

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

Возник вопрос, когда я изучал ответ на этот вопрос SO. Рассмотрим следующий код:

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-qualit) классом, рассматриваются функции преобразования. Применимое преобразование функции перечислены (13.3.1.5), и выбран лучший через разрешение перегрузки (13.3). Пользовательское преобразование selected вызывается для преобразования выражения инициализатора в объект инициализируется. Если преобразование не может быть выполнено или неоднозначный, инициализация плохо сформирована.
  • [...]

И в этом раунде разрешения перегрузки в разделе 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, в котором говорится, что вызов функции будет плохо сформирован из-за двусмысленности.

Есть ли что-то неправильное в приведенном выше анализе, или это предложение является ошибкой в ​​стандарте?

4b9b3361

Ответ 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. Если функция, использующая двусмысленный последовательность преобразования выбирается как лучшая жизнеспособная функция, вызов будет плохо сформирован, потому что преобразование одного из аргументов в вызове неоднозначно.