Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why constructor is not called for given casting operator?

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
}

When oB is constructed then Why B::B(A*) is NOT invoked for Wrap<T>::operator T () ? [Note: B::operator = (A*) is invoked for Wrap<T>::operator T () in the next statement]

like image 812
iammilind Avatar asked May 25 '11 06:05

iammilind


People also ask

Is constructor called when casting?

A constructor for a class type is called whenever a new instance of that type is created. If a cast creates a new object of that class type then a constructor is called.

Does static cast use constructor?

When you use static_cast , by defaut (i.e. without optimizations activated) it calls the conversion constructor of the object you are trying to cast into (if it exists). For instance, in this code. The highlighted expression would call the following constructor (if existent): Foo(const Bar &) .

Which operator is used for type casting?

Cast operator: () A type cast provides a method for explicit conversion of the type of an object in a specific situation.

What is the use of type cast operator in C++?

A Cast operator is an unary operator which forces one data type to be converted into another data type. Static Cast: This is the simplest type of cast which can be used.


2 Answers

The problem is that the number of user-defined conversions that are invoked implicitly is limited (to 1) by the Standard.

B ob = a;

implies two user conversions:

  • on a: Wrap<A>::operator A*() should be called
  • on the result: B::B(A*) should be called

@James Kanze's explanation: this syntax is called "copy initialization", effectively equivalent to B ob = B(a) (with the copy being elided most of the time). This is different from B ob(a) which is a "direct initialization" and would have worked.

if you explicitly qualify any of this, it will work, for example:

B ob = B(a);

On the other hand, for the second case there is no issue:

ob = a;

is short-hand for:

ob.operator=(a);

And thus only one user-defined conversion is required, which is allowed.

EDIT:

Since it's been required in a comment (to Kirill's answer) we can take a guess at the motive.

Chained conversions could be long, very long, and therefore:

  • could surprise users -- implicit conversions may already be surprising as it is...
  • could lead to an exponential search of the possibilities (for the compiler) -- it would need to go from both ends, trying to check all possible conversions, and somehow "join" the two (with the shortest path possible).

Furthermore, as long as there is more than 1 conversion, you run into the risk of having cycles, which would have to be detected (even though diagnostic would probably not be required, and be subject to Quality Of Implementation).

So, since a limit is necessary to avoid infinitely long searches (it could have been left unspecified, with a minimum required), and since beyond 1 we may have new issues (cycles), then 1 seems as good a limit as any after all.

like image 178
Matthieu M. Avatar answered Oct 12 '22 23:10

Matthieu M.


It's because you're using "copy initialization". If you write the declaration of oB:

B oB(a);

, it should work. The semantics of the two initializations are different. For B oB(a), the compiler tries to find a constructor which can be called with the given arguments. In this case, B::B(A*) can be called, because there is an implicite conversion from Wrap<A> to A*. For B oB = a, the semantics are to implicitly convert a to type B, then use the copy constructor of B to initialize oB. (The actual copy can be optimized out, but the legality of the program is determined as if it weren't.) And there is no implicit conversion of Wrap<A> to B, only of Wrap<A> to A*.

The assignment works, of course, because the assignment operator also takes a A*, and so the implicit conversion comes into play.

like image 30
James Kanze Avatar answered Oct 12 '22 23:10

James Kanze