MK.
MK.

Reputation: 34527

writing template for operator<< for any vector

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

Answers (7)

BiagioF
BiagioF

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

Justin
Justin

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

max66
max66

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

Stefano Buora
Stefano Buora

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

Curious
Curious

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

NathanOliver
NathanOliver

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

user4442671
user4442671

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

Related Questions