Question
C++ ambiguous member vs non-member function template resolution in GCC 14 but not in prior GCC versions
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.