7

I want to overload operator<< for both std::list and std::vector with the following code. But the two functions are almost the same. Is there any way to combine them, i.e., create a more generic overload?

#include <iterator>
#include <iostream>
#include <vector>
#include <list>

template <typename T>
std::ostream &operator<<(std::ostream &out, const std::vector<T> &v)
{
  if (!v.empty())
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
  return out;
}

template <typename T>
std::ostream &operator<<(std::ostream &out, const std::list<T> &v)
{
  if (!v.empty())
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
  return out;
}

int main()
{
  std::cout << std::vector<int>({1, 2, 3, 4}) << std::endl;
  std::cout << std::list<int>({1, 2, 3, 4}) << std::endl;
  return 0;
}
Christophe
  • 68,716
  • 7
  • 72
  • 138
D C dQ
  • 87
  • 5

2 Answers2

4

You can use template with template arguments like in the following example:

template <typename T, typename A, template <typename X, typename Y> class C> 
std::ostream &operator<<(std::ostream &os, const C<T,A> &container)
{
  if(!container.empty())
    std::copy(container.begin(), container.end(), std::ostream_iterator<T>(os, " "));
  return os;
}

int main() {
    list<int> l{1,2,3,4,5}; 
    vector<string> v{"one","two","three"};
    cout<<l<<endl<<v; 
    return 0;
}

Online demo.

Bu the way, you may find other example for working with templates of templates in this SO question.

But you have to be careful with this kind of construct:

  • it works only for containers defined with two template arguments (so ok for list and vectors; but not for sets or maps).
  • it might conflict with other template types using two arguments, for which there's no specialisation of the extractor.

Remark: If you look for a general purpose solution, you should better think of creating an adapter template that uses an iterator as parameter, and then write the general purpose extractor for this adaptor.

Christophe
  • 68,716
  • 7
  • 72
  • 138
4

Enter C++20 and the likely inclusion of Concepts and Ranges, the solution to your problem is something that could be simplified greatly.

A Concept is basically a template parameter with constraints, e.g.

// Taken from https://en.cppreference.com/w/cpp/experimental/constraints
template <typename T>
concept bool Integral = std::is_integral<T>::value;

template <Integral T> // Using concept Integral.
void foo(T i) { /* ... */ } // 'i' has to be integral, or compile time error.

Now, the concept of a Range (simplified) is something that complies to the interface (pseudocode):

Range {
    begin()
    end()
}

Using this, one could write something like the following:

template <Range T>
std::ostream& operator<<(std::ostream& out, T&& rng) {
    std::copy(std::forward<T>(rng), std::make_ostream_joiner(out, ", "));
    return out;
}

And it would just work for anything that has a begin() and end(), e.g.

std::cout << std::vector{1, 2, 3, 4, 5} << std::endl; // Ok
std::cout << std::list{1, 2, 3, 4, 5} << std::endl; // Ok

Additionally, note that I used std::make_ostream_joiner instead of std::ostream_iterator. It makes use of a new C++20 iterator std::ostream_joiner that writes the delimiter between every two objects skipping the extra trailing delimiter, i.e. you would get "1, 2, 3, 4, 5" instead of "1, 2, 3, 4, 5, ".

Let's hope all these features make it into C++20 :)

Note: All examples given are hypothetical C++20 code and does not currently compile using any release-build compiler that I know of.

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • Concepts are already in C++20 :) Also your syntax is wrong for your constrained `operator<<`. – Rakete1111 Jul 08 '18 at 13:19
  • @Rakete1111 Yes that is imo great, but is it set in stone? – Felix Glas Jul 08 '18 at 13:30
  • Well, nothing is set in stone. And yes it happened with C++0x concepts IIRC, but I don't know any other instances where a feature was removed after being included in the standard. So it's very unlikely – Rakete1111 Jul 08 '18 at 13:31
  • @Rakete1111 Could you please be more specific about what's wrong with my syntax? – Felix Glas Jul 08 '18 at 13:36
  • `Range&& rng` where `Range` is a concept is not valid. You want: `template std::ostream& operator<<(std::ostream&, T&& rng);`, as your constrained type is `T`, not `Range`. – Rakete1111 Jul 08 '18 at 13:40
  • @Rakete1111 You are indeed right, thanks for pointing out my mistake. – Felix Glas Jul 08 '18 at 13:43