Почему конструктор не вызывается для данного оператора приведения? - PullRequest
6 голосов
/ 25 мая 2011
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 () в следующем выражении]

Ответы [ 4 ]

10 голосов
/ 25 мая 2011

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

B ob = a;

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

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

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

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

B ob = B(a);

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

ob = a;

сокращенно для:

ob.operator=(a);

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

EDIT

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

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

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

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

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

7 голосов
/ 25 мая 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*, и поэтому в игру вступает неявное преобразование.

5 голосов
/ 25 мая 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.S

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

C cb = B(A(10);  //okay. no chained implicit conversion!
C ca = A(10);    //error, requires chained implicit conversion
2 голосов
/ 25 мая 2011

Это потому, что C ++ Standard допускает только одно пользовательское преобразование. Согласно разделу 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
...