user1899020 user1899020 -4 years ago 87
C++ Question

Why operator<< is ambiguous?

I defined a function template in global scope as

template<class T>
inline std::ostream& operator<<(std::ostream& os, T const& t)
{
return os;
}


Then

std::cout << "\n";


doesn't compile because of ambiguity. I think it is function overloading and the compile can resolve it, and choose the more specific version for
char const*
. Why doesn't compile?

Answer Source

Your version of operator<< here is templated on one argument, and so is the version supplied by the standard.

The problem lies in that your version (We'll call it function a) is templated on one argument:

template<class T>
inline std::ostream& operator<<(std::ostream& os, T const& t)
{       
    return os;
}

and from cppreference, you can see the standard's overload for const char* (we'll call it b) is templated on the other:

template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,  
                                        const char* s );

You can get the behavior you expect by making your version of operator<< use the same template argument for the first argument (this one is c):

template<class T, class CharT, class Traits>
inline std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, T const& t)
{       
        return os;
}

compiles happily, as you can see on coliru.

This is because template overload selection chooses the most specialized template when selecting an overload with multiple candidate template functions.

Specifically, first the template is transformed:

[temp.func.order]:

  1. Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.

  2. To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template. [ Note: The type replacing the placeholder in the type of the value synthesized for a non-type template parameter is also a unique synthesized type.  — end note ] ...

[temp.deduct.partial]:

  1. Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type. [ Note: The creation of the transformed type is described in [temp.func.order].  — end note ] The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.

  2. The types used to determine the ordering depend on the context in which the partial ordering is done:

    1. In the context of a function call, the types used are those function parameter types for which the function call has arguments.

...

  1. Function template F is at least as specialized as function template G if, for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G. F is more specialized than G if F is at least as specialized as G and G is not at least as specialized as F.

So, we want the parts of the templates that are the function arguments.

a) T                 =>  std::ostream&,                     T const&
b) CharT, Traits     =>  std::basic_ostream<CharT, Traits>, const char*
c) T, CharT, Traits  =>  std::basic_ostream<CharT, Traits>, T const&

For the first argument of these functions, a is more specialized than b and c.

For the second argument of these functions, b is more specialized than a and c.

As we learned in [temp.deduct.partial]/10, argument deduction requires all relevant arguments of a function f1 to be at least as specialized as all arguments of a function f2 for the function f1 to be at least as specialized as f2, a cannot here be more specialized than b, since each have one argument more specialized than the other's matching argument.

This isn't the case with c, as both a and c have all arguments at least as specialized as the arguments in c.

Thus, overload resolution will be ambiguous in choosing between a and b because a is at least as specialized as b and b is at least as specialized as a. The ambiguity is removed by using c instead of a because b is at least as specialized as c, but the reverse is not true.

As we know from [temp.func.order]/2, the more specialized function wins overload resolution, and with the use of c rather than a, the winner is b, and the line std::cout << "hello"; prints hello to the console.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download