I want to write template function which can print container like std::vector
, std::list.
Below is my function, just overload <<
.
@miradham explained the issue very well.
but this a more generic solution using SFINAE to make the overload considered only for types on which the range-based for loop can be used, what ever their template arguments can be.
type coming form std::basic_string
were ignored to prevent ambiguity with the standard operator <<
to display strings
c-style array will not be displayed using this overload even though they could because they are decayed to pointers and displayed with the standard operator <<
#include <iostream>
#include <vector>
#include <type_traits>
#include <array>
#include <string>
template<template<typename...> typename From, typename T>
struct is_from : std::false_type {};
template<template<typename...> typename From, typename ... Ts>
struct is_from<From, From<Ts...> > : std::true_type {};
template <typename...>
using void_t = void;
template <typename T, typename = void>
struct is_input_iterator : std::false_type { };
template <typename T>
struct is_input_iterator<T,
void_t<decltype(++std::declval<T&>()),
decltype(*std::declval<T&>()),
decltype(std::declval<T&>() == std::declval<T&>())>>
: std::true_type { };
template<typename Container,
typename std::enable_if<is_input_iterator<decltype(std::begin(std::declval<Container>()))>::value &&
is_input_iterator<decltype(std::end(std::declval<Container>()))>::value &&
!is_from<std::basic_string, Container>::value, int>::type = 0>
std::ostream& operator<<(std::ostream& out, const Container& c){
for(const auto& item:c){
out << item << " ";
}
return out;
}
int main(){
std::array<int, 6> arr{0, 1, 2, 3, 4, 5};
std::vector<int> vec{5, 9, 1, 4, 6};
std::cout << vec << std::endl;
std::cout << arr << std::endl;
std::cout << std::string("test") << std::endl;
return 0;
}
Declaring template<typename Container>
could be dangerous as this template includes 'all' variable types int
, char
etc. Due to this compiler does not know which operator<<
to use.
In order to take only container type variables use template of templates. Here is working code for you
template<typename T, template <typename, typename> class Container>
std::ostream& operator<<(std::ostream& out, const Container<T, std::allocator<T>>& c) {
for (auto item : c) {
out << item << " ";
}
return out;
}
int main()
{
cout << "Hello world" << endl;
int arr[] = { 0,3,6,7 };
vector<int> v(arr, arr+4);
cout << v << endl;
return 0;
}
The problem is that the templated type Container can match any type, not just containers. That includes the " "
you are trying to print.
If you look at the error message from a different compiler: https://godbolt.org/g/3YKtca
<source>:5:15: note: candidate function [with Container = char [2]]
std::ostream& operator<<(std::ostream& out, const Container& c){
Perhaps you want a partial specialization of vector<T>
to only take vectors. Determining if a type is a container is a more complicated problem.
#include <iostream>
#include <vector>
template<typename E, typename A>
std::ostream& operator<<(std::ostream& out, const std::vector<E, A>& c){
for(auto item:c){
out<<item;
out<<item<<" "; // error
}
return out;
}
int main(){
std::vector<int> iVec{5, 9, 1, 4, 6};
std::cout<<iVec<<std::endl;
return 0;
}
https://godbolt.org/g/NJNwmN
The problem is that operator<<
that you defined matches for both std::vector
and const char (&array)[N]
(the type of " "
that you try to stream to out
).
A simplified code example that demonstrates the problem:
#include <iostream>
template<typename Container>
std::ostream& operator<<(std::ostream& out, const Container& c)
{
return out;
}
int main()
{
std::cout<<" "<<std::endl;
return 0;
}
The following example would restrict operator<<
to std::vector
s only:
template<typename ... Args>
std::ostream& operator<<(std::ostream& out, const std::vector<Args...>& c)
{
for(auto item:c){
out<<item<<" ";
}
return out;
}
Live example.