Почему конструктор не вызывается для данного оператора приведения?

struct A {}; 
struct B
{
  B (A* pA) {}
  B& operator = (A* pA) { return *this; }
};

template<typename T>
struct Wrap
{
  T *x; 
  operator T* () { return x; }
};

int main ()
{
  Wrap<A> a;
  B oB = a; // error: conversion from ‘Wrap<A>’ to non-scalar type ‘B’ requested
  oB = a;  // ok
}

Когда создается oB, то почему B::B(A*) НЕ вызывается для Wrap<T>::operator T ()? [Примечание: B::operator = (A*) вызывается для Wrap<T>::operator T () в следующем операторе]


person iammilind    schedule 25.05.2011    source источник
comment
Какой компилятор? Компилируется нормально на VS2010.   -  person Agnel Kurian    schedule 25.05.2011
comment
@Agnel Kurian, это потому, что VS2010 частично поддерживает C++0x.   -  person Kirill V. Lyadvinsky    schedule 25.05.2011
comment
@Kirill: Итак, вышеизложенное действительно в C++0x? Это то, что вы имели в виду?   -  person Agnel Kurian    schedule 25.05.2011
comment
@Agnel Kurian, я только что посмотрел на C++0x, нет, это недействительно. Кажется, это расширение или ошибка в VS2010.   -  person Kirill V. Lyadvinsky    schedule 25.05.2011
comment
с gcc 4.4.3 я не могу B oB; скомпилировать, я должен сделать B oB(a);. Так что мне непонятно, к чему относится // ok в третьей строке main.   -  person Andre Holzner    schedule 25.05.2011
comment
@Andre, я упомянул в Примечании к своему вопросу, что означает третья строка.   -  person iammilind    schedule 25.05.2011
comment
MSVC выдаст предупреждение warning C4928: illegal copy-initialization; more than one user-defined conversion has been implicitly applied, но для его получения необходимо передать /Wall.   -  person Michael Burr    schedule 25.05.2011


Ответы (4)


Проблема в том, что количество пользовательских преобразований, которые вызываются неявно, ограничено (до 1) стандартом.

B ob = a;

подразумевает две пользовательские конверсии:

  • на a: следует вызвать Wrap<A>::operator A*()
  • по результату: B::B(A*) следует вызвать

Объяснение @James Kanze: этот синтаксис называется "инициализация копирования", фактически эквивалентный B ob = B(a) (при этом в большинстве случаев копия опускается). Это отличается от B ob(a), который является "прямой инициализацией" и сработал бы.

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

B ob = B(a);

С другой стороны, для второго случая нет проблем:

ob = a;

является сокращением для:

ob.operator=(a);

Таким образом, требуется только одно определяемое пользователем преобразование, которое разрешено.

ИЗМЕНИТЬ:

Поскольку это требовалось в комментарии (к ответу Кирилла), мы можем догадаться о мотиве.

Цепные преобразования могут быть длинными, очень длинными, и поэтому:

  • может удивить пользователей — неявные преобразования уже могут удивлять...
  • может привести к экспоненциальному поиску возможностей (для компилятора) - ему нужно будет идти с обоих концов, пытаясь проверить все возможные преобразования и каким-то образом «соединить» их (по кратчайшему пути).

Кроме того, если есть более одного преобразования, вы рискуете иметь циклы, которые должны быть обнаружены (хотя диагностика, вероятно, не потребуется и будет зависеть от качества реализации).

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

person Matthieu M.    schedule 25.05.2011
comment
+1 Правильный ответ. Стандарт не допускает связанного преобразования. Он допускает одно пользовательское преобразование в выражении. - person Nawaz; 25.05.2011
comment
Этот ответ верен лишь частично. Ключевым моментом здесь является семантика инициализации копированием, в отличие от прямой инициализации. - person James Kanze; 25.05.2011

Это потому, что вы используете «инициализацию копирования». Если вы пишете объявление oB:

B oB(a);

, он должен работать. Семантика этих двух инициализаций различна. Для B oB(a) компилятор пытается найти конструктор, который можно вызвать с заданными аргументами. В этом случае можно вызвать B::B(A*), потому что происходит неявное преобразование из Wrap<A> в A*. Для B oB = a семантика заключается в неявном преобразовании a в тип B, а затем использовании конструктора копирования B для инициализации oB. (Фактическая копия может быть оптимизирована, но законность программы определяется так, как если бы это было не так.) И нет неявного преобразования Wrap<A> в B, только Wrap<A> в A*.

Присваивание, конечно, работает, потому что оператор присваивания также принимает A*, поэтому в игру вступает неявное преобразование.

person James Kanze    schedule 25.05.2011
comment
+1 поднят хороший вопрос. У меня было ощущение, что B oB = a; и B oB(a) одинаковы! - person iammilind; 25.05.2011
comment
+1000. Хороший. Я не знал разницы между семантикой двух инициализаций. - person Nawaz; 25.05.2011

Стандарт не допускает связанного неявного преобразования. Если бы это было разрешено, то вы могли бы написать такой код:

struct A
{
   A(int i) //enable implicit conversion from int to A
};  
struct B
{
   B(const A & a); //enable implicit conversion from A to B
};
struct C
{
   C(const B & b); //enable implicit conversion from B to C
}; 
C c = 10; //error

Вы не можете ожидать, что 10 преобразуется в A, которое затем преобразуется в B, которое затем преобразуется в C.


B b = 10; //error for same reason!

A a = 10;        //okay, no chained implicit conversion!
B ba = A(10);    //okay, no chained  implicit conversion!
C cb = B(A(10)); //okay, no chained implicit conversion!
C ca = A(10);    //error, requires chained implicit conversion

То же правило применяется к неявному преобразованию, которое неявно вызывает operator T().

Учти это,

struct B {};

struct A 
{
   A(int i); //enable implicit conversion from int to A
   operator B(); //enable implicit conversion from B to A
};

struct C
{
   C(const B & b); //enable implicit conversion from B to C
}; 

C c = 10; //error

Вы не можете ожидать, что 10 преобразуется в A, который затем преобразуется в B (используя operator B()), который затем преобразуется в C. С

Такие связанные неявные преобразования не допускаются. Вы должны сделать это:

C cb = B(A(10);  //okay. no chained implicit conversion!
C ca = A(10);    //error, requires chained implicit conversion
person Nawaz    schedule 25.05.2011

Это связано с тем, что стандарт C++ допускает только одно определяемое пользователем преобразование. Согласно 12.3/4:

К одному значению неявно применяется не более одного определяемого пользователем преобразования (конструктор или функция преобразования).

B oB = a; // not tried: ob( a.operator T*() ), 1 conversion func+1 constructor
oB = a;   // OK: oB.operator=( a.operator T*() ), 1 conversion func+1 operator=

В качестве обходного пути вы можете использовать явную форму вызова конструктора:

B oB( a ); // this requires only one implicit user-defined conversion
person Kirill V. Lyadvinsky    schedule 25.05.2011
comment
вы можете подробнее немного больше. Особенно в чем причина того, что это не разрешено. - person iammilind; 25.05.2011