Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

prefer conversion operator over conversion constructor

Tags:

c++

I have the following code snippet:

class A
{
public:
  A() : x_(0), y_(0) {}

  A(int x, int y) : x_(x), y_(y) {}

  template<class T>
  A(const T &rhs) : x_(rhs.x_), y_(rhs.y_)
  {
  }

  int x_, y_;
};

class B
{
public:
  B() {}

  operator A() const { return A(c[0],c[1]); }
  int c[2];
};

void f()
{
  B b;
  (A)b; // << here the error appears, compiler tries to use 
        //         template<class T> A(const T &rhs)
}

Why compiler uses A's constructor? How can I make it use B's conversion operator to A?

I use MSVS2010 compiler. It gives me these errors:

main.cpp(9): error C2039: 'x_' : is not a member of 'B'
          main.cpp(17) : see declaration of 'B'
          main.cpp(28) : see reference to function template instantiation 'A::A<B>(const T &)' being compiled
          with
          [
              T=B
          ]
main.cpp(9): error C2039: 'y_' : is not a member of 'B'
          main.cpp(17) : see declaration of 'B'

UPD: All right, implicit convert as Nawaz said really works. Let's make it more complicated, how make the following code work?

void f()
{
  std::vector<B> b_vector(4);
  std::vector<A> a_vector( b_vector.begin(), b_vector.end() );
}

UPD: A is the class in 3rd party lib which code I can't edit, so I can't remove A's converting constructor.

UPD: the simplest solution I've found for the moment is to define specialization of converting constructor for B. It can be done outside of 3rd party lib:

template<> A::A( const B &rhs ) : x_(rhs.c[0]), y_(rhs.c[1]) {}
like image 871
pure cuteness Avatar asked Jun 20 '11 17:06

pure cuteness


3 Answers

The reason is not only because it thinks (A)b is same as A(b). The standard says this about explicit type-conversion (5.4):

The conversions performed by

  • a const_cast (5.2.11),

  • a static_cast (5.2.9),

  • a static_cast followed by a const_cast,

  • a reinterpret_cast (5.2.10), or

  • a reinterpret_cast followed by a const_cast,

can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply.

Essentially this means that even for explicit type-conversion of (A)b (i.e. if you used ((A)b); to prevent from it being a variable declaration). it'd use the rules of static_cast. Now let's take a look at what the standard says about static_cast (5.2.9):

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast(e) if the declaration “T t(e);” is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference type (8.3.2), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.

If you do static_cast<A>(b), it basically sees if A(b) is well-formed; and it is. Just because the actual instantiation of the template function copy-constructor fails, it doesn't make the actual declaration is ill-formed, hence it uses it and ultimately fails.

like image 84
reko_t Avatar answered Oct 14 '22 21:10

reko_t


From 5.4/1 and 5.4/5 the C-cast picks the "best choice" C++ cast from a list. In this case, that's a static_cast.

Then from 5.2.9/2:

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast(e) if the declaration “T t(e);” is well formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is a reference type (8.3.2), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.

So it picks the constructor before even attempting any other option.

In this case you've defined two conversions to get to the same end result, but the language has specific rules dictating that it will always use the available constructor. You should probably leave the constructor and change the operator to an explicit as-type function instead.

EDIT for OP's edit: I don't think you'll be able to use the vector iter, iter constructor. You'll need to start with an empty vector and either use a for loop with push_back or use std::transform.

like image 20
Mark B Avatar answered Oct 14 '22 21:10

Mark B


(A)b; // << here the error appears, compiler tries to use 

This is explicit casting to A. Hence the constructor of A gets invoked to convert b to A.

A a(1,2);
a = b ; //this will invoke user-defined conversion of B (implicit conversion)

Demo : http://www.ideone.com/K9IxT

like image 42
Nawaz Avatar answered Oct 14 '22 21:10

Nawaz