I am trying to build an operator <<
overload to print all the standard library container types. So far, the overload works well, but when I use it in a generic program, it breaks when I try to print a simple std::string
. In fact, this program:
#include <iostream>
#include <utility>
#include <vector>
#include <map>
#include <string>
// Helper Function to Print Test Containers (Vector and Map)
template <typename T, typename U>
inline std::ostream& operator<<(std::ostream& out, const std::pair<T, U>& p) {
out << "[" << p.first << ", " << p.second << "]";
return out;
}
template <template <typename, typename...> class ContainerType, typename
ValueType, typename... Args>
std::ostream& operator <<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
for (const auto& v : c) {
os << v << ' ';
}
return os;
}
int main()
{
std::vector <int> v = { 1,2,3 };
std::cout << v;
std::map <int,int> m = { { 1, 1} , { 2, 2 }, { 3, 3 } };
std::cout << m;
std::string s = "Test";
std::cout << s;
}
Gives me this error:
prove.cpp: In function ‘int main()’:
prove.cpp:32:13: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’})
32 | std::cout << s;
| ~~~~~~~~~ ^~ ~
| | |
| | std::string {aka std::__cxx11::basic_string<char>}
| std::ostream {aka std::basic_ostream<char>}
prove.cpp:16:15: note: candidate: ‘std::ostream& operator<<(std::ostream&, const ContainerType<ValueType, Args ...>&) [with ContainerType = std::__cxx11::basic_string; ValueType = char; Args = {std::char_traits<char>, std::allocator<char>}; std::ostream = std::basic_ostream<char>]’
16 | std::ostream& operator <<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
| ^~~~~~~~
In file included from /usr/include/c /9/string:55,
from /usr/include/c /9/bits/locale_classes.h:40,
from /usr/include/c /9/bits/ios_base.h:41,
from /usr/include/c /9/ios:42,
from /usr/include/c /9/ostream:38,
from /usr/include/c /9/iostream:39,
from prove.cpp:1:
/usr/include/c /9/bits/basic_string.h:6419:5: note: candidate: ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, const std::__cxx11::basic_string<_CharT, _Traits, _Allocator>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’
6419 | operator<<(basic_ostream<_CharT, _Traits>& __os,
| ^~~~~~~~
I think that the problem is due to the fact that this overload is used to print also simple std::string
objects, but I didn't find any suitable way to solve this so far. Any help? thanks.
CodePudding user response:
Your operator<<
overload may match types for which an operator<<
overload is already defined. I suggest that you disable it for such types:
#include <type_traits>
template <template <typename, typename...> class ContainerType,
typename ValueType, typename... Args>
std::enable_if_t<!is_streamable_v<ContainerType<ValueType, Args...>>,
std::ostream&>
operator<<(std::ostream& os, const ContainerType<ValueType, Args...>& c) {
for (const auto& v : c) {
os << v << ' ';
}
return os;
}
The enable_if_t
line uses SFINAE to disable the function for types that are already streamable.
The type trait is_streamable_v
that is used above could look like this:
template<class T>
struct is_streamable {
static std::false_type test(...);
template<class U>
static auto test(const U& u) -> decltype(std::declval<std::ostream&>() << u,
std::true_type{});
static constexpr bool value = decltype(test(std::declval<T>()))::value;
};
// helper variable template:
template<class T>
inline constexpr bool is_streamable_v = is_streamable<T>::value;
Your original program should now work as expected:
int main() {
std::vector <int> v = { 1,2,3 };
std::cout << v;
std::map <int,int> m = { { 1, 1} , { 2, 2 }, { 3, 3 } };
std::cout << m;
std::string s = "Test";
std::cout << s;
}
CodePudding user response:
The issue is that your <<
is too generic. It matches std::cout << some_string;
and potentially other types for which there is already a <<
overload. The solution is to not provide overloads for types you do not own (unless it is explicitly permitted).
With minimal changes you can refactor your <<
to be a named function, then write a wrapper:
template <typename T>
struct my_print {
T& t;
};
tempalte <typename T>
std::ostream& operator<<(std::ostream& out,const my_print<T>& mp) {
// call your named funtion template
do_the_printing(out,mp);
return out;
}
The caveat is that you need to explicitly create the instance:
std::cout << my_print(some_stl_container);
Of course you can also let your function template print directly and call it like this
do_the_print(std::cout,some_stl_container);