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
const
by using a standard conversion sequence (consisting on just one qualification conversion).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.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.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
V<float>
. For that, we have a user-defined conversion sequence:S<float>
to const S<float>
via qualification conversion.const S<float>
to V<float>
via V<float>(const S<T> & s)
constructor.V<float>
to const V<float>
via qualification conversion.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?
V<float>
. For that, we have a user-defined conversion sequence:S<float>
to const S<float>
via qualification conversion.const S<float>
to V<float>
via V<float>(const S<T> & s)
constructor.V<float>
to const V<float>
via qualification conversion. 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 :)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With