views:

158

answers:

3

Given the following function templates:

#include <vector>
#include <utility>

struct Base { };
struct Derived : Base { };

// #1
template <typename T1, typename T2>
void f(const T1& a, const T2& b)
{
};

// #2
template <typename T1, typename T2>
void f(const std::vector<std::pair<T1, T2> >& v, Base* p)
{
};

Why is it that the following code always invokes overload #1 instead of overload #2?

void main()
{
    std::vector<std::pair<int, int> > v;
    Derived derived;

    f(100, 200);  // clearly calls overload #1
    f(v, &derived);         // always calls overload #1
}

Given that the second parameter of f is a derived type of Base, I was hoping that the compiler would choose overload #2 as it is a better match than the generic type in overload #1.

Are there any techniques that I could use to rewrite these functions so that the user can write code as displayed in the main function (i.e., leveraging compiler-deduction of argument types)?

+8  A: 

Given that the second parameter of f is a derived type of Base

It's convertible to such, but it is a Derived*. The first template function requires no conversions, and the second requires one, therefore it chooses the first.

This chooses the second:

f(v, static_cast<Base*>(&derived));

On a side note, main returns an int.

GMan
Thank you for the explanation. I've removed the return value from main as it wasn't necessary in the example.
Steve Guidi
Well main has an implicit `return 0;` so you can leave int anyway :) And it's one character shorter! :P
GMan
Necessary or not, C++ language _requires_ that 'main' is declared with return type 'int'.
AndreyT
+1  A: 

Beside the obvious topics about Koenig Lookup that is more or less well implemented by compilers (especially older ones are quite problematic), there are a few pitfalls regarding template specialization.

The specialization requires the types to exactly match (not sure how the std defines this, but from my experience [gcc, msvc] a derived class won't be matched). If you add an ugly cast to Base*, it should work like you intend, optionally add another specialization for Derived...

jdehaan
+10  A: 

You can either do this:

f(v, static_cast<Base*>(&derived));

Or use SFINAE to remove the first function as a selection candidate:

// Install boost library and add these headers:
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>

// #1 - change it to look like this (note the keyword void changed positions)
template <typename T1, typename T2>
typename boost::disable_if<
   typename boost::is_convertible<T2, Base*>, void>::type
f(const T1& a, const T2& b)
{
};

// #2 - this one can stay the same
template <typename T1, typename T2>
void f(const std::vector<std::pair<T1, T2> >& v, Base* p)
{
};
Dennis
Boost to the rescue! Very clever!
Steve Guidi
Dang I was just adding this. :)
GMan
The only sad note is that it is so much less readable... +1 anyway, explaining is good, proposing a solution is even better :)
Matthieu M.
I'm hoping for a `enable_ifa` in c++0x :) Now that we have template aliases, i wonder whether this wouldn't work: `template<typename T, typename R = void> using enable_ifa = typename boost::enable_if<T, R>::type;` So that we could then do `template<typename T> enable_ifa<is_foo<T>> f(T t);` would surely make things a lot more readable :)
Johannes Schaub - litb
The readability hit is unfortunate. I think C++0x Concepts would have don what litb wants; you could have had 2 templates and say that one's type arguments have to fit a certain Concept and the other's type arguments must satisfy a different Concept. Unfortunately, in a move that stunned many of us who follow the C++ standards committee as spectators, the committee ripped Concepts out of the proposal at the last minute. There were reasons it had to be done, too long to explain here. Google for Stroustroup on Concepts removal if you're interested.
Dennis