Question

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 :)

Was it helpful?

Solution

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.

OTHER TIPS

When considering template matches, implicit conversions are not used. Therefore, in the following simple example:

template < typename T >
void foo( T t1, T t2 ) { /* do stuff */ }

int main( int argc, char ** argv ) {
    foo( 1, 1.0 );
    return 0;
}

That will not compile even though either argument could be implicitly converted to the other type (int <-> double).

Just a guess, but perhaps the compiler cannot distinguish between the conversion from V->S or from S->V while trying to figure out how to add a + c in expression 2. You're assuming the compiler will be smart enough to pick the one which allows the compilation to proceed because of the rest of the available functions, but the compiler is probably not "reading ahead" (so to speak), and is getting confused with the ambiguity of the up-conversion before trying to find the '+' operator.

Of course, if you added the compilation error, it might help clarify the problem too...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top