Reputation: 6462
I am trying to write a custom string class MyString
with implicit conversion from const char*
.
In addition to that, I want to output MyString
via the shift operators into any std::ostream
. Even more, I want it to output into a custom stream class that also supports the shift operators. I am trying to resolve that situation by using a template that works for MyStream
just the same as with std::ostream
.
However, this leads easily to ambiguity in overload resolution. The gist of the problem is summarized in the following example.
#include <iostream>
#include <sstream>
struct MyString
{
// implicit constructors
MyString( const char* cstr ) {};
// some dummy c_str()
const char* c_str() const { return nullptr; };
};
// // This works just fine, as expected, ...
// inline std::ostream& operator<<( std::ostream& out, const MyString& str )
// {
// std::cout << str.c_str();
// return out;
// }
// ... but this one causes ambiguity !!!
template<typename S>
inline S& operator<<( S& out, const MyString& str )
{
std::cout << str.c_str();
return out;
}
int main()
{
std::cout << "1";
std::stringstream ss;
ss << "2";
std::cout << ss.str().c_str();
return 0;
}
The code above works just fine. However, if instead I comment out the std::ostream
operator overload and try to use the template instead, then the operator calls are suddenly ambiguous.
The shift into std::cout
works but the shift into std::stringstream ss
has become ambigious.
Question: what is the reason for the lack of ambiguity in the first case and its the sudden ambiguity in the second case? What possibilities are there to resolve this issue?
I want to retain the implicit conversion facility for MyString
, and I want to shift such objects into anything that behaves like the standard stream, using the shift operators.
EDIT: Whether it compiles or not depends on the compiler's strictness. On https://www.onlinegdb.com/online_c++_compiler I get
main.cpp: In function ‘int main()’:
main.cpp:33:11: warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
33 | ss << "2";
| ^~~
In file included from /usr/include/c++/11/iostream:39,
from main.cpp:1:
/usr/include/c++/11/ostream:611:5: note: candidate 1: ‘std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, const char*) [with _Traits = std::char_traits<char>]’
611 | operator<<(basic_ostream<char, _Traits>& __out, const char* __s)
| ^~~~~~~~
main.cpp:21:11: note: candidate 2: ‘S& operator<<(S&, const MyString&) [with S = std::__cxx11::basic_stringstream<char>]’
21 | inline S& operator<<( S& out, const MyString& str )
| ^~~~~~~~
12
Upvotes: 0
Views: 107
Reputation: 256
std::ostream
is a base type of std::stringstream
defined as std::basic_stringstream<char>
(see the inheritance diagram).
The instantiated function operator<<(std::stringstream& out, const MyString& str)
conflicts with the function operator<<(std::ostream& out, const char*s)
because the compiler can't decide whether to convert the const char*
argument to MyString
or to upcast std::stringstream
to std::ostream
.
We may explicitly convert the string argument to remove the ambiguity, i.e. ss << MyString("2");
Update: Note that the first function called with cout
argument is the standard function and not the expected one, because the standard function represents the exact match, https://godbolt.org/z/P46sxejr4.
I suggest to not rely on implicit conversions for the listed in the documentation types because there is always will be a better exact standard match. And I suggest to stick to the recommended way of defining the overload to make your code less error-prone.
Upvotes: 1