2015-05-27 2 views
12

У меня есть два класса, один из которых, например, представляет собой строку, а другой может быть преобразован в строку:Неоднозначное назначение оператора

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*). Почему разница?

+3

Безумие. Это все, что я могу сказать о самых глубоких ямах C++ .... –

ответ

5

Существует разница между инициализации и присвоения.

В инициализации, то есть:

A y = x; 

Фактический вызов зависит от типа x. Если это тот же самый тип y, то это будет как:

A y(x); 

Если не так, как в вашем примере, это будет как:

A y(static_cast<const A&>(x)); 

И это нормально компилируется, потому что есть нет никакой двусмысленности.

В задании нет такого особого случая, поэтому автоматическое разрешение двусмысленности отсутствует.

Стоит отметить, что:

A y(x); 

также неоднозначна в вашем коде.

+3

Возможно, стоит отметить, что в этом описании есть некоторые упрощения. Например, 'static_cast' на самом деле является неявным преобразованием, которое не допускает явных функций преобразования. – dyp

+0

@ dyp True. Для точного, но плотного объяснения есть ответ Колумба. – rodrigo

4

Там находится §13.3.1.4/(1,2), только приходящаяся инициализации() от копирования объектов типа класса, который определяет, как функции преобразования кандидата для первого случая найдены:

Под условия, указанные в 8.5, как часть инициализации копии объекта типа типа , может быть вычислено определяемое пользователем преобразование для преобразования выражения инициализатора в тип инициализированного объекта . Разрешение перегрузки используется для , чтобы выбрать вызываемое пользователем преобразование. [...] Если предположить, что «CV1 T» типа объекта инициализации, с T типа класса функция кандидатов выбираются следующим образом:

  • конвертерной конструкторы (12.3.1) из T являются кандидаты функции.

  • Когда тип выражения инициализатора является тип класса «резюмеS», не-явные функции преобразования из S и его базовых классов рассматриваются. При инициализации временного объекта, связанного с , первым параметром конструктора, где параметр имеет тип «ссылка на возможный cv-квалификатор T», а конструктор называется с единственным аргументом в контексте прямой инициализации объект типа «cv2T», явные функции преобразования также относятся к . Те, которые не скрыты в пределах S, и дают тип , чья неквалифицированная версия имеет тот же тип, что и T, или является производным классом , являются функциями-кандидатами. [...] Функции преобразования, возвращающие «ссылку на X» значения возврата или значения x, в зависимости от типа ссылки, типа 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 или (во всех случаях),

определяемые пользователем последовательности преобразования не рассматриваются. [Примечание: Эти правила предотвращают применение более одного пользовательского преобразования во время разрешения перегрузки, тем самым избегая бесконечной рекурсии. - конец примечание]

+0

TL; DR: Стандарт написан таким образом, что в последовательности преобразования никогда не существует двух пользовательских преобразований. – dyp

+0

@ dyp Ах, так вы говорите, что последовательность преобразования не включает вызов конечного конструктора, но он включает вызов конструктора подразумеваемого временного, с которым инициализируется объект? – Columbo

+0

'T t = x;' - это определение неявного преобразования. Пусть 'X' - тип' x'. Мы можем разрешить преобразование (функцию) из 'X' непосредственно в' T' ИЛИ конструктор 'T', который принимает' X'. В противном случае будет промежуточное преобразование из 'X' в' U' в 'T'. Инициализация 'T t (x);' не является неявным преобразованием, интересно, считается ли это конверсией вообще. – dyp

Смежные вопросы