Reputation: 34527
I'm trying to write a template operator for any iterable container. Getting a strange error:
#include <iostream>
template <typename C>
std::ostream& operator<<(std::ostream& os, const C& c) {
os << "[";
for (const auto& v : c) {
os << v << " ";
}
os << "]";
return os;
}
vec.cc:5:6: error: use of overloaded operator '<<' is ambiguous (with operand types 'std::ostream' (aka 'basic_ostream') and 'const char [2]') os << "["; ~~ ^ ~~~
Why this error? And how do I achieve what I want?
Upvotes: 7
Views: 1083
Reputation: 9715
The ambiguity comes from the fact that your template overloads the operator<<
for the std::ostream
and multiple definitions can be used.
Practically, for example, the standard library defines:
std::ostream& operator<<(std::ostream&, const char*);
Your function will match the same with template substitution typename C = const char*
.
In order to solve this ambiguity you can either adopt another design choice (for example, avoid to overload operator<<
) or solve the ambiguity specialising the template function.
Many other answers can be good in that context, I'd suggest you another option (quite less verbose, but with some limitations. See below).
Since you're trying to print Containers, it's likely that a container is a template class (std::vector<T>
, std::list<T>
, etc...). So your function can be slightly modified in order to handle only types which are template classes.
template <template <typename, typename...> class Container, typename T,
typename... Args>
std::ostream& operator<<(std::ostream& os, const Container<T, Args...>& c) {
os << "[";
for (const auto& v : c) {
os << v << " ";
}
os << "]";
return os;
}
Note that code will requires C++11 because of variadic templates.
Here an example to show that ambiguity has been solved.
Note the limitation in that solution is, as said, that it works only with container which are template classes. And it does not work with std::array
because the structure of array is different.
Upvotes: 0
Reputation: 25327
You get this error because your function matches every call to <<
with a std::ostream&
on the left.
template <typename C> std::ostream& operator<<(std::ostream& os, const C& c) { os << "["; for (const auto& v : c) { os << v << " "; } os << "]"; return os; }
When you write os << "["
, the compiler finds multiple operator<<
functions to call; your's is one of them. By adding a global operator<<
that's templated to take any type, you intercept basically every call to operator<<
.
The cleanest way you could do this is to define a new function, say print_collection
:
template <typename C>
void print_collection(std::ostream& os, const C& c) {
os << "[";
for (const auto& v : c) {
os << v << " ";
}
os << "]";
}
If you really want to define an operator<<
, this gets more tricky. You could do this:
template <typename C>
std::ostream& operator<<(std::ostream& os, const std::vector<C>& c) {
os << "[";
for (const auto& v : c) {
os << v << " ";
}
os << "]";
return os;
}
However, if the standard library decides to add an operator<<
of their own for std::vector
, your code will break.
I'd strongly recommend that if you wanted to add such an operator<<
, you do it for your own type. Something like this:
template <typename Iter>
class Range {
Iter begin_;
Iter end_;
public:
Range() = default;
Range(Iter begin, Iter end)
: begin_{ begin }
, end_{ end }
{}
auto begin() const { return begin_; }
auto end() const { return end_; }
};
template <typename Iter>
auto range(Iter begin, Iter end) {
return Range<Iter>{ begin, end };
}
template <typename C>
auto range(const C& collection) {
return range(std::begin(collection), std::end(collection));
}
template <typename Iter>
std::ostream& operator<<(std::ostream& os, const Range<Iter>& range) {
os << "[";
for (const auto& v : range) {
os << v << " ";
}
os << "]";
return os;
}
Then you could use it like this:
std::vector<int> vec = ...;
std::cout << range(vec);
Upvotes: 3
Reputation: 66210
As explained by others, for os << "["
your operator introduce an ambiguity.
I don't know if it's a good idea but I propose the use of SFINAE to enable your operator only for types supporting begin()
.
I mean
template <typename C>
auto operator<<(std::ostream& os, const C& c)
-> decltype( c.begin(), os ) {
os << "[";
for (const auto & v : c) {
os << v << " ";
}
os << "]";
return os;
}
Upvotes: 0
Reputation: 1062
I believe that your definition of the operator << is causing an ambiguity with the standard operator defined for a C++ stream.
I'd try to use some SFINAE technique changing your prototype in something like:
template <typename C>
std::ostream& operator<<(std::ostream& os, const C& c, C::const_iterator fakeVar = c.begin() ) {
os << "[";
for (const auto& v : c) {
os << v << " ";
}
os << "]";
return os;
}
It should make your template suitable only if it can be compiled
Upvotes: 2
Reputation: 21510
The error occurs because your operator<<
matches a lot of other overloads in the standard library. In your case with the overload meant to go with const char[2]
.
If you want this to work with any iterable container, one way is to constrain it to check for the validity of begin()
and end()
methods on it
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
template <typename Container,
std::enable_if_t<std::is_same<
decltype(std::declval<Container>().begin()),
decltype(std::declval<Container>().begin())>::value>* = nullptr>
std::ostream& operator<<(std::ostream& os, const Container& container) {
os << "[";
for (const auto& ele : container) {
os << ele << " ";
}
os << "]";
return os;
}
int main() {
auto vec = std::vector<int>{1, 2, 3};
cout << vec << endl;
}
Upvotes: 3
Reputation: 180650
Adding
template <typename C>
std::ostream& operator<<(std::ostream& os, const C& c) {
os << "[";
for (const auto& v : c) {
os << v << " ";
}
os << "]";
return os;
}
Conflicts with the other global overloads of operator <<
.
To fix this we can constrain your template to any vector instead of any type using
template <typename C>
std::ostream& operator<<(std::ostream& os, const std::vector<C>& c) {
os << "[";
for (const auto& v : c) {
os << v << " ";
}
os << "]";
return os;
}
Upvotes: 9
Reputation:
At this line:
os << "[";
The compiler finds two valid functions: the STL's and yours.
You need to be a bit more specific in your template declaration in order to resolve the conflict:
template <typename C>
std::ostream& operator<<(std::ostream& os, const std::vector<C>& c) {
...
Expanding this to containers in general would require a bit of finagling with std::enable_if<>
that will probably just confuse you further. I recommend you just add an overload for each type of container you want to support.
Edit: Also, overriding ostream << T
, for types you don't own is generally a bad idea as it will eventually cause conflicts.
Upvotes: 5