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;
}
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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With