Overloaded template method not resolved as expected

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 tries to instantiate the Logger& operator<<(const T& value)

overload, which of course fails with static_assert

. Why is Logger& operator<<(const CSizex<T>& size)

n't it considered the best match? How can I implement what I want?

+3


source to share


1 answer


Indeed, it const CSizex<T>&

is an exact match, since it is an identity transformation; [Over.ics.ref] / 1:

When a parameter of a reference type is bound directly to an argument expression, an implicit conversion sequence is an identity conversion, unless the argument expression is of type that is a derived class of the parameter's type, in which case the implicit conversion sequence is a derived base conversion (13.3.3.1) ...

The specialization generated from the template that throws the error is also an exact match: [Over.ics.user] / 4:

Converting a class type expression to the same class type specified rank Exact match and converting a class type expression to a base class of that type is assigned a conversion rank despite the fact that a copy / move constructor (i.e. a user-defined conversion function) is called for these cases ...

However, the first pattern is more specialized than the second. After removing the 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

, will Unique

output the transformed template argument to the original template parameter of another template: deduced to CSizex<T>

, which leads to deduction failure (since Unique

it is not a specialization CSizex

), whereas CSizex<Unique>

, derived against T

, will be successful (if T

will be CSizex<Unique>

itself). Thus, the first template is more specialized and therefore must be selected through partial ordering.
 Clang compiles this correctly. So GCC 4.9.0... You may have reduced your problem to code that no longer reflects the error.


Updated:

Now 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 inferred as CSize

, so a parameter CSize const&

, and for # 1, a specialization parameter CSize<int> const&

. The above quote clearly states the overload resolution:

When a parameter of a reference type is bound directly to an argument expression, an implicit conversion sequence is an identity conversion, unless the argument expression is of type that is a derived class of the parameter's type, in which case the implicit conversion sequence is a derived base conversion (13.3.3.1) ...

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

The conversion received on the basis has a conversion rank (13.3.3.1.1).

... and identity transformations are of exact match rank.

Basically, it's enough to move the condition of the static statement to 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 .

+2


source







All Articles