Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload operator for both std::vector and std::list

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;
}
like image 444
D C dQ Avatar asked Jul 08 '18 10:07

D C dQ


2 Answers

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.

like image 116
Christophe Avatar answered Sep 27 '22 22:09

Christophe


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.

like image 24
Felix Glas Avatar answered Sep 27 '22 20:09

Felix Glas