Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make C++ call the right template method in an un-ugly way

Tags:

c++

templates

I'm cooking up a vector library and have hit a snag. I want to allow recursive vectors (i.e. vec<H,vec<W,T> >) so I'd like my "min" and other functions to be recursive as well. Here's what I have:

template<typename T>
inline T min(const T& k1, const T& k2) {
 return k1 < k2 ? k1 : k2;
}
template<int N, typename T, typename VT1, typename VT2>
inline vec<N,T> min(const container<N,T,VT1>& v1, const container<N,T,VT2>& v2) {
 vec<N,T> new_vec;
 for (int i = 0; i < N; i++) new_vec[i] = min(v1[i], v2[i]);
 return new_vec;
}

...

template<int N, typename T>
class vec : public container<N,T,vec_array<N,T> > {

...

// This calls the first (wrong) method and says you can't call ? on a vec
vec<2,float> v1,v2;
min(v1,v2);
// This says the call is ambiguous
container<2,float,vec_array<2,float> > c1,c2;
min(c1,c2);
// This one actually works
vec<2,float> v3; container<N,T,some_other_type> v4;
min(v3,v4);
// This works too
min<2,float,vec_array<2,float>,vec_array<2,float> >(v1, v2);

That last call is ugly! How can I call the right method with just min(v1,v2)? The best I can come up with is to get rid of the "vec" class (so v1 and v2 have to be defined as container<2,float,vec_array<2,float> >) and add one more template<N,T,VT> min method that calls min<N,T,VT,VT>(v1,v2).

Thanks!

like image 997
Chris Avatar asked Sep 08 '10 03:09

Chris


1 Answers

You are going to have an overload resolution that prefers the first min for the first case. It accepts both arguments by an exact match, while the second min needs a derived to base conversion to accept arguments.

As you have subsequently figured out (by experimentation?), if you use container<...> as argument types, instead of derived classes, this won't need a derived to base conversion anymore, and overload resolution will then prefer the second template because otherwise both are equally well accepting the arguments but the second template (In your own solution) is more specialized.

Yet in your own solution, you need to put a typename before the return type to make the solution Standard C++. I think the problem that causes you to need to define a second template is that in order to make the template more specialized, the first min min needs to accept all the arguments that the second template accepts, which is figured out by just trying to match second template's arguments against first

container<N, T, VT1> -> T // func param 1
container<N, T, VT2> -> T // func param 2

So, the different template parameter types try to deduce to the same template parameter, which will cause a conflict and make the first template not successfully deduce all argument of the second template. For your own solution, this won't be the case:

container<N, T, VT> -> T // func param 1
container<N, T, VT> -> T // func param 2

This will make the first template deduce all the parameter types from the second template, but not the other way around: container<N, T, VT> won't match an arbitrary T. So your own solution's template is more specialized and is called, and then explicitly forwards to the other template.

Finally note that your own solution only accepts containers where the third template argument is the same, while your other min template accepts containers where that argument can be different for both function arguments. I'm not sure whether that's on purpose - but given the other min function in place which conflicts if you won't make the third argument types the same as shown above, I'm not sure how to otherwise fix that.


Questioner subsequently edited his own answer , so most of my references above to "your own answer" don't apply anymore.

like image 178
Johannes Schaub - litb Avatar answered Sep 29 '22 16:09

Johannes Schaub - litb