Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloaded template method not resolved as expected

Tags:

c++

templates

I have the following code:

#include <iostream>
#include <type_traits>

template <typename T> struct CSizex {
};

struct CSize : public CSizex<int> 
{
};

struct Logger
{
    template <typename T> 
    Logger& operator<< (T& value)
    {
        return *this << const_cast<const T & >(value);
    }

    template <typename T> Logger& operator<<(const CSizex<T>& size)
    {
        std::cout << __FUNCSIG__;
        return *this;
    }

    template <typename T> 
    Logger& operator<< (const T& value)
    {
        static_assert(std::is_arithmetic<T>::value || std::is_integral<T>::value || std::is_enum<T>::value, "This method is only meant for arithmetic types");
        std::cout << __FUNCSIG__;
        return *this;
    }
};

int main()
{
    CSize size;
    Logger() << CSize();
    return 0;
}

When I do this:

Logger() << CSize();

the compiler is trying to instantiate Logger& operator<<(const T& value) overload, which, of course, fails with static_assert. Why isn't Logger& operator<<(const CSizex<T>& size) considered a better match? How can I implement what I want?

like image 715
Violet Giraffe Avatar asked Mar 08 '26 17:03

Violet Giraffe


1 Answers

In a fact, const CSizex<T>& is an exact match as it is an identity conversion; [over.ics.ref]/1:

When a parameter of reference type binds directly 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).

The specialization instantiated from the template that causes the error is also an exact match: [over.ics.user]/4:

A conversion of an expression of class type to the same class type is given Exact Match rank, and a conversion of an expression of class type to a base class of that type is given Conversion rank, in spite of the fact that a copy/move constructor (i.e., a user-defined conversion function) is called for those cases.

However, the first template is more specialized than the second. After removing references and cv-qualifiers from the parameters and argument types of both the original and the transformed templates we then, for a unique type Unique, deduce the argument of the transformed template against the parameter of the original template of the other template: Unique is deduced against CSizex<T>, which yields a deduction failure (since Unique is no specialization of CSizex), while CSizex<Unique> deduced against T would be successful (with T being CSizex<Unique> itself). So the first template is more specialized and should thus be selected by partial ordering.
Clang compiles this correctly. So does GCC 4.9.0. Perhaps you reduced your problem to a code that does not reflect the mistake anymore.


Updated:

Now, we consider

template <typename T>
Logger& operator<<(const CSizex<T>& size); // #1

template <typename T> 
Logger& operator<< (const T& value);       // #2

For #2, the argument is deduced as CSize so the parameter is CSize const&, while for #1 the parameter of the specialization is CSize<int> const&. Overload resolution clearly states in the above quote:

When a parameter of reference type binds directly 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).

... that the conversion is an identity conversion for #2 but a derived-to-base conversion for #1. It is not hard to see that #2 is selected due to the better rank, [over.best.ics]/6:

A derived-to-base Conversion has Conversion rank (13.3.3.1.1).

... and identity conversions have Exact Match rank.

Basically it should suffice to move the condition of the static assertion into enable_if:

struct Logger
{
    template <typename T>
    Logger& operator<<(const CSizex<T>& size)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        return *this;
    }

    template <typename T>
    typename std::enable_if<std::is_arithmetic<T>::value
                         || std::is_integral<T>::value 
                         || std::is_enum<T>::value, Logger&>::type
    operator<<(const T& value)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        return *this;
    }
};

Demo.

like image 130
Columbo Avatar answered Mar 10 '26 05:03

Columbo