views:

79

answers:

3

Hello again :)

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

A: 

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...

Nick
Well, according to the standard the operator+ is a candidate function because it has the correct number of arguments. So the compiler should be able to trace the conversion I just wrote... or so I understood. As for the error message: ../main.cpp:42: error: no match for ‘operator+’ in ‘a + c’ (sorry, I don't know how to put new lines here)
As I said, that would require the compiler to "read ahead" and convert c using the V constructor, before it was able to match the custom operator+ specified. Since there's no direct matching operator, and no definitive up-conversion (which might need to be intrinsic, not sure), the compiler gives up. That's my guess on the compiler logic, anyway.
Nick
Clarification: you don't have a candidate operator, because your operator cannot be template-instantiated to match the types being added without invoking custom conversion operators. That's my understanding.
Nick
+3  A: 

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

Edric
You are right in that the code you provide does not work. But why? I cannot see a reason on the standard, and actually http://accu.org/index.php/articles/268 says "n most cases a function template behaves just like a normal function when considering overload resolution". And finally we have the thing that implicit conversion does happen in the expression "b=c"
By the way, the reason your code does not work is a problem of instantiation, not implicit conversion. If you call "foo" with foo<int>( 1, 1.0 ) there is no problem compiling.
Oh, after reading sellibitze's answer I realize you were probably trying to tell me just that... I didn't understand, sorry.
Yep, sorry if I was too terse!
Edric
+2  A: 

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.

sellibitze
Thanks you very very much!! I was looking at the problem all wrong from the beginning... thanks you also very much for the "friend operator" trick, I think that's the what I'll do :)