Неоднозначный оператор присваивания
У меня есть два класса, один из которых, скажем, представляет строку, а другой может быть преобразован в строку:
class A {
public:
A() {}
A(const A&) {}
A(const char*) {}
A& operator=(const A&) { return *this; }
A& operator=(const char*) { return *this; }
char* c;
};
class B {
public:
operator const A&() const {
return a;
}
operator const char*() const {
return a.c;
}
A a;
};
Теперь, если я делаю
B x;
A y = x;
Он запускает конструктор копирования, который компилируется отлично. Но если я делаю
A y;
y = x;
Он жалуется на двусмысленное присваивание и не может выбирать между =(A&)
и =(char*)
. Почему разница?
Ответы
Ответ 1
Существует разница между инициализацией и присваиванием.
При инициализации, то есть:
A y = x;
Фактический вызов зависит от типа x
. Если это тот же тип y
, то он будет выглядеть так:
A y(x);
Если нет, как в вашем примере, это будет выглядеть так:
A y(static_cast<const A&>(x));
И это прекрасно компилируется, потому что больше нет двусмысленности.
В присваивании нет такого особого случая, поэтому автоматическое разрешение двусмысленности отсутствует.
Стоит отметить, что:
A y(x);
также неоднозначен в вашем коде.
Ответ 2
Существует §13.3.1.4/(1.2), только относящийся к (copy-) инициализации объектов типа класса, который определяет, как будут найдены функции преобразования кандидатов для вашего первого случая:
В условиях, указанных в 8.5, как часть копирование-инициализация объекта типа класса, определяемая пользователем преобразование может быть вызвано для преобразования выражения инициализатора в тип инициализированного объекта. Разрешение перегрузки используется для выберите пользовательское преобразование для вызова. […] При условии, что "cv1 T
" - это тип инициализированного объекта, с T
классом тип, выбранные функции выбираются следующим образом:
-
Конструкторы преобразования (12.3.1) of T
являются кандидатами функции.
-
Когда тип выражения инициализатора является типом класса "cv S
", неявные функции преобразования S
и его базы классы. При инициализации временной привязки к первый параметр конструктора, где параметр имеет тип "ссылка на возможно cv-qual T
", и конструктор называется с одним аргументом в контексте прямой инициализации объект типа "cv2 T
", явные функции преобразования также считается. Те, которые не скрыты внутри S
и выдают тип чья cv-неквалифицированная версия имеет тот же тип, что и T
, или является производным класс является функциями-кандидатами. [...] Функции преобразования, возвращающие "ссылку на X
" return lvalues или xvalues, в зависимости от типа ссылки, типа X и, следовательно, считается, что для этого процесса выбора кандидатских функций требуется X
.
т.е. operator const char*
, хотя и рассматривается, не входит в набор кандидатов, поскольку const char*
явно не похож на A
в любом отношении. Однако во втором фрагменте operator=
вызывается как обычная функция-член, поэтому это ограничение больше не применяется; Когда обе функции преобразования находятся в наборе кандидатов, разрешение перегрузки явно приведет к двусмысленности.
Обратите внимание, что для прямой инициализации указанное правило также не применяется.
B x;
A y(x);
Неправильно сформирован.
Более общая форма этого результата состоит в том, что в одной последовательности преобразования никогда не может быть двух определяемых пользователем преобразований при разрешении перегрузки. Рассмотрим §13.3.3.1/4:
Однако, если цель
- первый параметр конструктора или [...]
и конструктор [...] является кандидатом по
- 13.3.1.3, когда аргумент является временным на втором этапе инициализации экземпляра класса или
- 13.3.1.4, 13.3.1.5 или 13.3.1.6 (во всех случаях),
пользовательские последовательности преобразований не рассматриваются. [Примечание: эти правила предотвращают применение более одного пользовательского преобразования во время разрешения перегрузки, тем самым избегая бесконечной рекурсии. - конец примечание]