Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversion not happening

The last question I asked was something I stumbled upon when trying to understanding another thing... that I also can't understand (not my day).

This is quite a long question statement, but at least I hope this question might prove useful to many people and not only me.

The code I have is the following:

template <typename T> class V;
template <typename T> class S;

template <typename T>
class V
{
public:
 T x;

 explicit V(const T & _x)
 :x(_x){}

 V(const S<T> & s)
 :x(s.x){}
};

template <typename T>
class S
{
public:
 T &x;

 explicit S(V<T> & v)
 :x(v.x)
 {}
};

template <typename T>
V<T> operator+(const V<T> & a, const V<T> & b)
{
 return V<T>(a.x + b.x);
}

int main()
{
 V<float> a(1);
 V<float> b(2);
 S<float> c( b );

 b = a + V<float>(c); // 1 -- compiles
 b = a + c;           // 2 -- fails
 b = c;               // 3 -- compiles

 return 0;
}

Expressions 1 and 3 work perfectly, while expression 2 does not compile.

If I have understood properly, what happens is:

Expression 1

  1. c is is implicitly converted to const by using a standard conversion sequence (consisting on just one qualification conversion).
  2. V<float>(const S<T> & s) is called and a temporal the const V<float> object generated (let's call it t). It is already const-qualified because it is a temporal value.
  3. a is converted to const similarly to c.
  4. operator+(const V<float> & a, const V<float> & b) is called, resulting in a temporal of type const V<float> that we can call q.
  5. the default V<float>::operator=(const & V<float>) is called.

Am I OK up to here? If I made even the most subtle mistake please, let me know, for I am trying to gain an understanding about implicit casting as deep as possible...

Expression 3

  1. c is converted to V<float>. For that, we have a user-defined conversion sequence:
    1.1. first standard conversion: S<float> to const S<float> via qualification conversion.
    1.2. user-defined conversion: const S<float> to V<float> via V<float>(const S<T> & s) constructor.
    1.3 second standard conversion: V<float> to const V<float> via qualification conversion.
  2. the default V<float>::operator=(const & V<float>) is called.

Expression 2?

What I do not understand is why there is a problem with the second expression. Why is the following sequence not possible?

  1. c is converted to V<float>. For that, we have a user-defined conversion sequence:
    1.1. initial standard conversion: S<float> to const S<float> via qualification conversion.
    1.2. user-defined conversion: const S<float> to V<float> via V<float>(const S<T> & s) constructor.
    1.3. final standard conversion: V<float> to const V<float> via qualification conversion.
  2. Steps 2 to 6 are the same as in case of expression 1.

After reading the C++ standard I though: 'hey! maybe the problem has to to with 13.3.3.1.2.3!' which states:

If the user-defined conversion is specified by a template conversion function, the second standard conversion sequence must have exact match rank.

But that cannot be the case since the qualification conversion has exact match rank...

I really have no clue...

Well, whether you have the answer or not, thanks you for reading up to here :)

like image 885
jmeseguerdepaz Avatar asked Oct 08 '10 06:10

jmeseguerdepaz


1 Answers

As Edric pointed out, conversions are not considered during template argument deduction. Here, you have two contexts where the template parameter T can be deduced from the type of the arguments:

template<class T>
v<T> operator+(V<T> const&, V<T> const&);
               ~~~~~~~~~~~  ~~~~~~~~~~~~

But you try to invoke this function template with a V<float> on the left-hand side and an S on the right hand side. Template argument deduction results in T=float for the left hand side and you'll get an error for the right hand side because there is no T so that V<T> equals S<T>. This qualifies as a template argument deduction failure and the template is simply ignored.

If you want to allow conversions your operator+ shouldn't be a template. There is the following trick: You can define it as an inline friend inside of the class template for V:

template<class T>
class V
{
public:
   V();
   V(S<T> const&); // <-- note: no explicit keyword here

   friend V<T> operator+(V<T> const& lhs, V<T> const& rhs) {
      ...
   }
};

This way, the operator is not a template anymore. So, there is no need for template argument deduction and your invocation should work. The operator is found through ADL (argument dependent lookup) because the left-hand side is a V<float>. The right-hand side is properly converted to a V<float> as well.

It is also possible to disable template argument deduction for a specific argument. For example:

template<class T>
struct id {typedef T type;};

template<class T>
T clip(
   typename id<T>::type min,
   T value,
   typename id<T>::type max )
{
   if (value<min) value=min;
   if (value>max) value=max;
   return value;
}

int main() {
   double x = 3.14;
   double y = clip(1,x,3); // works, T=double
}

Even though the type of the first and last argument is an int, they are not considered during template argument deduction because id<T>::type is not a so-called *deducible context`. So, T is only deduced according to the second argument, which results in T=double with no contradictions.

like image 106
sellibitze Avatar answered Sep 28 '22 02:09

sellibitze