I am trying to understand ostream overloads. Consider this
#include <iostream>
using std::ostream;
enum class A{a1, a2, a3};
template <class T>
ostream& operator<<(ostream& out, const T& a)
{
switch(a)
{
case T::a1 :
return out<<"a1";
case T::a2 :
return out<<"a2";
case T::a3 :
return out<<"a3";
};
return out;
}
/*ostream& operator<<(ostream& out, const A& a)
{
switch(a)
{
case A::a1 :
return out<<"a1";
case A::a2 :
return out<<"a2";
case A::a3 :
return out<<"a3";
};
return out;
}*/
int main()
{
A a = A::a3;
std::cout<<a<<std::endl;
}
While compiling i get error as below
test.cpp:13:17: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘const char [3]’)
return out<<"a1";
^
While uncommenting normal function and commenting template version works fine. Why ambiguity is not in normal function and why it is in templatized version
The non-template operator does not cause any ambiguity because that operator itself is not viable for resolving this call:
return out << "a1";
// ^^^^^^^^^^^
// This MUST be `std::operator <<`, no other valid overload of
// operator << is found!
As well as the other similar ones.
The template version, on the other hand, is viable, since T
is not bound to be any concrete type:
template <class T>
ostream& operator<<(ostream& out, const T& a)
{
switch(a)
{
case T::a1 :
return out << "a1";
// ^^^^^^^^^^^
// Here the compiler could invoke std::operator <<
// OR it could invoke your operator << template,
// which is also viable since T could be anything!
// Which one should it pick?
// ...
}
}
Therefore, the compiler does not know whether to pick the overload in the std
namespace or your function template (yes, that would be an attempt to establish an infinite recursion, but the compiler doesn't need to care).
Those overloads are both good, hence the ambiguity.
One way to fix your problem would be to SFINAE-constraint your template overload of operator <<
so that it is considered for overload resolution only when T
is an enumeration type. For instance:
#include <type_traits>
template <class T,
typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
ostream& operator<<(ostream& out, const T& a)
Here is a live example.
As Andy Prowl wrote in their answer, the problem is due to an unintentional overload ambiguity introduced by your code because there are now two suitable overloads for out<<"a1"
(and also out<<"a2"
and out<<"a3"
), one from std
and one being the very overload you've defined, which the compiler has a hard time choosing between.
An alternative solution, in addition to the one already described, would be to choose the desired overload explicitly with the using
declaration:
template <class T>
ostream& operator<<(ostream& out, const T& a)
{
using std::operator<<;
switch(a)
{
...
That will convey your intention to use the "standard" version of the function to the compiler, thus removing the ambiguity.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With