hochl
hochl

Reputation: 12920

Template function gets called instead of function of base type

I have a class hierarchy that can be written to an object using operator<<. The example looks as follows:

#include <iostream>

struct Base
{
};

struct Derived : public Base
{
};

struct Shift
{
    template< typename U >
    Shift& operator<<(const U&)
    {
        std::cerr << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }

    Shift& operator<<(const Base&)
    {
        std::cerr << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }

#if 0
    Shift& operator<<(const Derived&)
    {
        std::cerr << __PRETTY_FUNCTION__ << std::endl;
        return *this;
    }
#endif
};

int main()
{
    Shift sh;
    Base bas;
    Derived der;
    int u32 = 0;

    sh << bas;
    sh << der;
    sh << u32;
}

This produces the following output:

Shift& Shift::operator<<(const Base&)
Shift& Shift::operator<<(const U&) [with U = Derived]
Shift& Shift::operator<<(const U&) [with U = int]

If I uncomment the #if 0 section it will change to the desired output:

Shift& Shift::operator<<(const Base&)
Shift& Shift::operator<<(const Derived&)
Shift& Shift::operator<<(const U&) [with U = int]

I have a lot of derived classes (actually a whole hierarchy) and until now I have to write a separate definition of operator<< for all those types. What I'd like is to have a solution where the operator for the base type is called for all types that are derived from Base. Is that possible?

P.S.: I tried several solutions, for example writing a helper class:

struct Base
{
};

struct Derived : public Base
{
};

template< typename T >
struct Helper
{
    static void shift()
    {
        std::cerr << __PRETTY_FUNCTION__ << std::endl;
    }
};

template< >
struct Helper< Base >
{
    static void shift()
    {
        std::cerr << __PRETTY_FUNCTION__ << std::endl;
    }
};

struct Shift
{
    template< typename U >
    Shift& operator<<(const U& value)
    {
        Helper< U >::shift();
        return *this;
    }
};

Output:

static void Helper<Base>::shift()
static void Helper<T>::shift() [with T = Derived]
static void Helper<T>::shift() [with T = int]

But still the base template is called, instead of the Base specialization.

P.P.S.: I'm currently limited to C++03 without Boost, unfortunately.

Upvotes: 1

Views: 106

Answers (1)

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48447

In your current implementation the templated overload is preferred as per §13.3.3.1.4 [over.ics.ref]/p1:

When a parameter of reference type binds directly (8.5.3) to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion (13.3.3.1).

Since the derived-to-base Conversion is given a Conversion rank, to achieve the desired output the templated version with an identity conversion rank must be excluded from the set of viable functions during the overload resolution by using enable_if with a proper condition (SFINAE):

#include <type_traits>

//...
template <typename U>
auto operator<<(const U&)
    -> typename std::enable_if<!std::is_base_of<Base, U>{}, Shift&>::type
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    return *this;
}

Shift& operator<<(const Base&)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    return *this;
}

Output:

Shift& Shift::operator<<(const Base&)
Shift& Shift::operator<<(const Base&)
typename std::enable_if<(! std::is_base_of<Base, U>{}), Shift&>::type Shift::operator<<(const U&) [with U = int; typename std::enable_if<(! std::is_base_of<Base, U>{}), Shift&>::type = Shift&]

In the implementation is less readable:

template <bool B, typename T = void>
struct enable_if { typedef T type; };
template <typename T>
struct enable_if<false, T> {};
template <typename Base, typename Derived>
struct is_base_of
{
    typedef char (&yes)[1];
    typedef char (&no)[2];
    static yes test(Base*);
    static no test(...);
    static const bool value = sizeof(test((Derived*)0)) == sizeof(yes);
};
template <typename Base, typename Derived>
const bool is_base_of<Base, Derived>::value;

//...
template <typename U>
typename enable_if<!is_base_of<Base, U>::value, Shift&>::type operator<<(const U&)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    return *this;
}

Shift& operator<<(const Base&)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    return *this;
}

Upvotes: 5

Related Questions