Какие из этих преобразований должны быть неоднозначными?
У меня есть код, подобный следующему:
class bar;
class foo
{
public:
operator bar() const;
};
class bar
{
public:
bar(const foo& foo);
};
void baz() {
foo f;
bar b = f; // [1]
const foo f2;
bar b2 = f2; // [2]
}
GCC выдает ошибку в [2], но не [1]. Clang выдает ошибку на обоих, и, очевидно, MSVC выдает ошибку ни на одном. Кто прав?
1 ответ
ТЛ; др
Неоднозначность. (Кроме того, если вы останавливаетесь на TL; Dr, то language-lawyer
тег не может быть вашей чашкой чая. ^_^)
Спойлер
У обоих кандидатов есть один const foo&
параметр, который одинаково связывается с const foo
или же foo
аргумент. Не появляются другие правила, которые бы предпочли ту или иную функцию.
Разбив его с текущим рабочим проектом C++
Инициализаторы [dcl.init]
В обоих случаях
- мы выполняем инициализацию копирования ( [dcl.init] / 15)
- тип назначения является типом класса ( [dcl.init] /17.6)
- (X) выражение инициализатора не является prvalue ( [dcl.init] /17.6.1)
- (X) тип источника не совпадает или не является производным от типа назначения ( [dcl.init] /17.6.2)
- Пользовательские последовательности преобразования перечисляются из [over.match.copy] и лучше всего выбираются по разрешению перегрузки.
Инициализация копии класса с помощью пользовательского преобразования [over.match.copy]
T
тип инициализируется, в обоих случаях это bar
, S
тип выражения инициализатора, в двух случаях foo
а также const foo
соответственно.
- преобразование конструкторов
T
являются кандидатами ( [over.match.copy] /1.1)bar::bar(const foo& foo);
является кандидатом
- тип выражения инициализатора
_cv_ S
поэтому рассматриваются неявные функции преобразования: ( [over.match.copy] /1.2)foo::operator bar() const
не спрятан внутриfoo
или в пределахconst foo
и даетbar
который так же, какT
и, следовательно, является кандидатом.
Таким образом, наш список кандидатов одинаков в обоих случаях:
bar::bar(const foo& foo)
foo::operator bar() const
В обоих случаях у нас есть пользовательское преобразование, состоящее из:
- Стандартное преобразование типа источника в определяемый пользователем аргумент преобразования
- Определяемое пользователем преобразование (одна из двух вышеуказанных функций) в тип результата
- Стандартное преобразование типа результата в целевой тип
Если мы выберем конструктор, то "тип результата" будет "предварительным значением cv-неквалифицированной версии целевого типа, чей результирующий объект инициализируется конструктором" ( [dcl.init] /17.6.3), поэтому для обоих кандидатов функции, второе Стандартное Преобразование - Идентичность (bar
-> bar
).
Разрешение перегрузки [over.match]
Подмножество жизнеспособных функций-кандидатов [over.match.viable]
Согласно [dcl.init] /17.6.3, выражение инициализатора будет аргументом для выбранного вызова, в двух случаях foo
а также const foo
соответственно.
bar::bar(const foo& foo)
- Один аргумент в списке аргументов, ровно один параметр. ( [over.match.viable] /2.1)
- Нет связанных ограничений ( [over.match.viable] / 3)
- Последовательность неявного преобразования существует из обоих
foo
а такжеconst foo
вconst foo&
( [over.match.viable] / 4) - Первоначальным стандартным преобразованием является преобразование идентичности в обоих случаях: [over.best.ics] / 5 => [over.ics.ref] / 1 для привязки прямой ссылки:
const foo
совместим с обоимиfoo
а такжеconst foo
какconst
более квалифицированным, чем обаconst
и ничего. [dcl.init.ref] / 4const foo&
привязывается непосредственно к lvaluefoo
и lvalueconst foo
, [dcl.init.ref]/5
- жизнеспособный
foo::operator bar() const
- Один аргумент в списке аргументов, ровно один неявный параметр объекта. ( [over.match.viable] /2.1)
- Неявный параметр объекта
const foo&
в обоих случаях ( [over.match.funcs] / 4)
- Неявный параметр объекта
- Нет связанных ограничений ( [over.match.viable] / 3)
- Последовательность неявного преобразования существует из обоих
foo
а такжеconst foo
вconst foo&
( [over.match.viable] / 4) - Первоначальным стандартным преобразованием является преобразование идентичности в обоих случаях, см. Выше.
- жизнеспособный
Выберите лучшую жизнеспособную функцию [over.best.ics]
И то, и другое - Identity => User Defined Conversion => Identity, т. Е. Определенные пользователем последовательности преобразования.
Ранжирование конверсионных последовательностей через .ics.rank
Можем ли мы установить ранжирование между последовательностями? Только если применяется одно из следующих
- (X) Не последовательности инициализации списка ( [over.ics.rank] / 3)
- (X) Не стандартная последовательность конвертации ( [over.ics.rank] /3.2)
- (X) Эти две последовательности не содержат "одну и ту же пользовательскую функцию преобразования или конструктор или [...] инициализируют один и тот же класс в совокупной инициализации" ( [over.ics.rank] /3.3)
Последовательности преобразования неразличимы, т. Е. Ни лучше, ни хуже
Лучшая жизнеспособная функция over.match.best
Является ли любая из этих функций "лучшей"? Только если применяется одно из следующих
- (X) Ни одна из них не является лучшей последовательностью преобразования ( [over.match.best] /1.3)
- (X) Второе стандартное преобразование такое же ( [over.match.best] /1.4)
- (X) Не прямая ссылка, привязывающая ссылку к типу функции ( [over.match.best] /1.5)
- (X) Ни одна из них не является специализацией шаблона функции ( [over.match.best] /1.6 и [over.match.best] /1.7)
- (X) Ни один из них не ограничен ( [over.match.best] /1.8)
- (X) Один конструктор для
bar
, но другой не является конструктором для базового классаbar
( [over.match.best] /1.9) - (X) Нет выведенных типов классов ([[over.match.best] /1.10] ( http://eel.is/c++draft/over.match.best#1.10 и [over.match.best] /1.11
- (X) Ни один из них не является шаблоном конструктора ( [over.match.best] /1.12)
Ни одна из них не является "лучшей" функцией, поэтому вызов плохо сформирован. [over.match.best] / 2