The following code compiles fine with GCC 13 and earlier, but GCC 14.1 produces an "ambiguous overload" error. Is it a compiler or code problem, and, more pragmatically, can I make the compiler prefer the non-member template by making changes in namespace ns
(staying in the c++11 land)?
#include <iostream>
#include <sstream>
#include <string>
//=======================================================================
namespace ns {
struct B
{
std::ostringstream os_;
~B()
{
std::cerr << os_.str() << '\n';
}
template<typename T>
B& operator<<(T const& v)
{
this->f(v);
this->os_ << ' ';
return *this;
}
private:
void f(int v) { os_ << v; }
void f(std::string const& v) { os_ << "\"" << v << '\"'; }
};
struct D : public B
{};
}
//==============================================================
namespace nsa {
struct A
{
int i;
std::string s;
};
template<typename S>
S& operator<<(S&& s, A const & a)
{
s << "S<<A" << a.i << a.s;
return s;
}
}
//==============================================================
int main()
{
ns::D() << "XX" << nsa::A{1, "a"};
}
GCC 13 compiles it successfully and the program output is
"XX" "S<<A" 1 "a"
The GCC 14 compiler output:
In function 'int main()':
<source>:50:19: error: ambiguous overload for 'operator<<' (operand types are 'ns::B' and 'nsa::A')
50 | ns::D() << "XX" << nsa::A{1, "a"};
| ~~~~~~~~~~~ ^~ ~~~~~~~~~
| | |
| ns::B nsa::A
<source>:17:8: note: candidate: 'ns::B& ns::B::operator<<(const T&) [with T = nsa::A]'
17 | B& operator<<(T const& v)
| ^~~~~~~~
<source>:41:6: note: candidate: 'S& nsa::operator<<(S&&, const A&) [with S = ns::B&]'
41 | S& operator<<(S&& s, A const & a)
| ^~~~~~~~
I thought the absence of other B::f()
would lead to a substitution failure, taking the member operator<<() template out of the overload set.
Multiple versions of clang think that it's an ambiguous overload.
MSVC seems to try to convert A to an int
or to a string
as if it doesn't see the non-member template at all, and outputs something like
<source>(22): error C2664: 'void ns::B::f(const std::string &)': cannot convert argument 1 from 'const T' to 'int'
with
[
T=nsa::A
]
<source>(22): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
<source>(53): note: see reference to function template instantiation 'ns::B &ns::B::operator <<<nsa::A>(const T &)' being compiled
with
[
T=nsa::A
]
The answer to this question seems to explain the ambiguity, although there are no calls to non-existing functions there, so I would not expect SFINAE to kick in in that example.
Adding an enable_if
to the member template does not seem to work very well, because there can be types convertible to int
for which one may want to optionally define a non-member template.
Is it a compiler or code problem?
A code problem.
Diagnostic of gcc 14 is pretty clear With
ns::D() << "XX" << nsa::A{1, "a"};
// ^^
We have "ns::B& << nsa::A
" (ns::D
is "lost" with ns::D() << "XX"
which returns ns::B
)
and we have 2 equally overloads (exact match)
ns::B::operator<< (const T&)
with T == nsa::A
nsa::operator<< (T&&, const A&)
with T == ns::B&
neither is more specialized than the other.
SFINAE doesn't happens on body.
You might apply SFINAE, which remove ns::B::operator<<
from viable function, removing ambiguity for your case:
template<typename T>
auto operator<<(T const& v)
-> decltype(this->f(v), *this)
{
this->f(v);
this->os_ << ' ';
return *this;
}