views:

143

answers:

2

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!

A: 
template<typename T1, **typename T2**>
inline T1 min(const T1& k1, **const T2&** k2) {
    return k1 < k2 ? k1 : k2;
}

...

template<int N, typename T>
struct vec { 
    typedef container<N,T,vec_array<N,T> > t;
};


...

vec<2,float>::t v1,v2;
min(v1,v2);

That's what I finally did to get it to work.

The ambiguity was because both arguments have the same type - container<2,float,vec_array<2,float> >. That's one point for the min(const T&,const T&) method. Since min(const container<N,T,VT1>& v1, const container<N,T,VT2>& v2) is a match and more specialized, it also got an extra point and the compiler couldn't make up its mind over which one to use. Switching the generic min to use two type arguments - min(const T1&, const T2&) - beats it into submission.

I also switched to using a "template typedef" instead of inheritance to define vec<N,T>'s without having to deal with the messy container<N,T,VT> stuff. This makes vec<N,T>::t be an exact match to the correct function.

Now that I'm using a typedef rather than inheritance and two types in the generic min function instead of just one, the correct method is getting called.

Chris
+1  A: 

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.

Johannes Schaub - litb
Johannes Schaub - litb: I am still confused about the ambiguity in the second call in OP (which I think is equivalent to) template<class T> struct A{};template<class T> struct B : A<T>{};template<class T>void min(T const min(b1, b2);}
Chubsdad
Johannes Schaub - litb