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

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

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

Другие вопросы по тегам