Должна ли инициализация функцией преобразования быть двусмысленной, если два кандидата имеют одинаковую квалификацию?

Оба clang и gcc принимают следующий код и выбирают A::operator B*.

struct B
{
};

struct A : B
{
    operator A*();
    operator B*();
};

A a;
void* x = a;

Мое чтение стандарта - особенно предложения, выделенные ниже жирным шрифтом - предполагает, что это преобразование должно быть неоднозначным.

Оба A::operator A* и A::operator B* являются кандидатами на разрешение перегрузки, поскольку A* и B* оба конвертируются в void* через стандартное преобразование. Поскольку подразумеваемый параметр объекта A& является единственным аргументом, рассматривается только последовательность преобразования, которая преобразуется из аргумента подразумеваемого объекта в параметр подразумеваемого объекта - тип, предоставляемый функцией преобразования, игнорируется. В обоих случаях подразумеваемый аргумент объекта представляет собой тип выражения инициализатора A, а параметр подразумеваемого объекта - A&. Если обе последовательности преобразования идентичны, нет возможности различать двух кандидатов.

8.5 Инициализаторы [dcl.init]

Семантика инициализаторов такова. Тип назначения - тип объекта или ссылки, являющийся инициализируется, а тип источника - тип выражения инициализатора.

- Если тип назначения является [reference/array/class...] [удаленные данные, не применимые к этому сценарию]

- В противном случае, если тип источника является (возможно, cv-qualit) типом класса, рассматриваются функции преобразования. Применяемые функции преобразования перечисляются (13.3.1.5), а лучший выбирается путем перегрузки (13.3). Выбранное пользователем преобразование вызывается для преобразования инициализатора выражение в инициализированный объект. Если преобразование невозможно или неоднозначно, инициализация плохо сформирована.

13.3.1.5 Инициализация с помощью функции преобразования [over.match.conv]

В условиях, указанных в 8.5, как часть инициализации объекта типа некласса, преобразование функция может быть вызвана для преобразования выражения инициализатора типа класса в тип объекта, являющегося инициализируется. Разрешение перегрузки используется для выбора функции преобразования, которая должна быть вызвана. Предполагая, что "cv1 T" - тип инициализируемого объекта, а "cv S" - тип выражения инициализатора, причем S a тип класса, выбранные функции выбираются следующим образом:

- Рассматриваются функции преобразования S и его базовые классы. Те неявные преобразования функции, которые не скрыты внутри S и выдают тип T или тип, который может быть преобразован в тип T через стандартную последовательность преобразования (13.3.3.1.1) являются кандидатными функциями. Для прямой инициализации эти явные функции преобразования, которые не скрыты внутри S, и дают тип T или тип, который может быть преобразованные в тип T с квалификационным преобразованием (4.4), также являются кандидатными функциями. преобразование функции, возвращающие тип с квалификацией cv, считаются выпущенными cv-неквалифицированной версией этого типа для этого процесса выбора кандидатских функций. Функции преобразования, возвращающие "ссылку на cv2 X" возвращают lvalues ​​или xvalues, в зависимости от типа ссылки, типа "cv2 X" и поэтому считается, что для этого процесса выбора кандидатских функций X получает X.

Список аргументов имеет один аргумент, который является выражением инициализации. [Примечание. Этот аргумент будет по сравнению с неявным параметром объекта функций преобразования. -end note]

Является ли это неоднозначным в соответствии со стандартом?

РЕДАКТИРОВАТЬ: обратите внимание, что это аналогичный вопрос, но не тот, что Отличие между пользовательскими последовательностями преобразования по начальной стандартной последовательности конверсий

Отличие состоит в том, что в моем примере обе функции преобразования имеют одинаковую квалификацию.

Ответы

Ответ 1

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


Все ссылки относятся к ISO/IEC 14882: 2011 (С++ 11). Поведение инициализации:

void* x = a;

определяется следующим образом. Во-первых, это инициализация, как описано в 8.5 Initializers [dcl.init], и соответствует грамматике, описанной в p1. Поскольку тип назначения void* является неклассовым типом, а тип источника A является типом класса, этот конкретный инициализатор имеет форму, описанную в 8.5 p16, bullet 7:

В противном случае, если тип источника является (возможно, cv-qualit) классом, рассматриваются функции преобразования. Применяемые функции преобразования перечислены (13.3.1.5), а наилучшая выбирается с помощью разрешения перегрузки (13.3). Выбранное пользователем преобразование вызывается для преобразования выражения инициализатора в инициализированный объект. Если преобразование не может быть выполнено или неоднозначно, инициализация плохо сформирована.

"Перечисление применимых функций преобразования" подробно описано в 13.3.1.5 p1:

В условиях, указанных в 8.5, как часть инициализации объекта типа некласса, преобразование функция может быть вызвана для преобразования выражения инициализатора типа класса в тип объекта, являющегося инициализируется. Разрешение перегрузки используется для выбора функции преобразования для вызова. Предполагая, что "cv1 T" - тип инициализируемого объекта, а "cv S" - тип выражения инициализатора, причем S a тип класса, выбранные функции выбираются следующим образом:

  • Рассматриваются функции преобразования S и его базовые классы. Те неявные преобразования функции, которые не скрыты внутри S и выдают тип T или тип, который может быть преобразован в тип T через стандартную последовательность преобразования (13.3.3.1.1) являются функциями кандидата. Для прямой инициализации эти явные функции преобразования, которые не скрыты внутри S, и дают тип T или тип, который может быть преобразованные в тип T с квалификационным преобразованием (4.4), также являются кандидатными функциями. преобразование функции, возвращающие тип с квалификацией cv, считаются выпущенными cv-неквалифицированной версией этого типа для этого процесса выбора кандидатских функций. Функции преобразования, возвращающие "ссылку на cv2 X" возвращают lvalues ​​или xvalues, в зависимости от типа ссылки, типа "cv2 X" и поэтому считается, что для этого процесса выбора кандидатских функций X получает X.

Обратите внимание, что как A::operator A*(), так и A::operator B*() являются функциями-кандидатами, поскольку оба A* и B* могут быть конвертированы в void* за 4.10p2: указатель "prvalue of type" на cv T, где T - тип объекта, может быть преобразован в указатель типа "указатель" к cv void "." Учитывая, что обе функции являются кандидатами, разрешение перегрузки должно выбирать между ними.

Разрешение перегрузки описывается в 13.3 [over.match]. p2:

Разрешение перегрузки выбирает функцию для вызова в семи разных контекстах языка:

...

  • вызов функции преобразования для инициализации объекта типа nonclass из выражения типа класса (13.3.1.5)

...

Каждый из этих контекстов определяет набор функций-кандидатов и список аргументов своим уникальным способом. Но, как только функции кандидата и списки аргументов были идентифицированы, выбор лучшей функции во всех случаях одинаковый:

  • Сначала подмножество функций-кандидатов (те, которые имеют правильное количество аргументов и удовлетворяют некоторым другим условиям) выбирается так, чтобы сформировать набор жизнеспособных функций (13.3.2).

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

Какая из наших двух функций жизнеспособна? 13.3.2 [over.match.viable] p1:

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

Требования представлены в p2:

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

и p3:

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

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

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

Как насчет этой финальной пули? Имеет ли одна из наших перегрузок более качественная стандартная последовательность преобразования от ее возвращаемого типа до void*?

13.3.3.2. Ранжирование неявных последовательностей преобразования [over.ics.rank] p4 указывает во второй точке маркера:

Если класс B получается прямо или косвенно из класса A, преобразование B* в A* лучше, чем преобразование B* в void*, а преобразование A* в void* лучше, чем преобразование B* в void*.

Это точно в случае OP, за исключением случаев, когда имена A и B отменены. Разрешение перегрузки для двух операторов преобразования OP разрешается в пользу A::operator B*(), поскольку указанное правило делает последовательность преобразования B*void* лучше, чем A*void*