I have a C++ class "X
" which would have special meaning if a container of them were to be sent to a std::ostream
.
I originally implemented it specifically for std::vector<X>
:
std::ostream& operator << ( std::ostream &os, const std::vector<X> &c )
{
// The specialized logic here expects c to be a "container" in simple
// terms - only that c.begin() and c.end() return input iterators to X
}
If I wanted to support std::ostream << std::deque<X>
or std::ostream << std::set<X>
or any similar container type, the only solution I know of is to copy-paste the entire function and change only the function signature!
Is there a way to generically code operator << ( std::ostream &, const Container & )
?
("Container
" here would be any type that satisfies the commented description above.)
For extra flexibility, streaming operators can be used in the cases where the bit ordering is important or a simple bit-stream cast is not sufficient.. There are two streaming operators, {>> {}} and {<< {}}, which operate on data blocks (or slices).
How to Pack Data Using the SystemVerilog Streaming Operators (>>, <<) 1 1. Pack bytes into an int. 2 2. Reverse the elements of a byte array and pack them into an int. 3 3. Reverse the bits in a byte. 4 4. Reverse the nibbles in a byte. 5 5. Reverse the bits of an array and pack them into a shortint. More items
Back to your first question, in your case it does not matter which side the streaming operator appears because both sides are a single variables of the same size. There is one slight difference when the variables are not the same size as mentioned in section 11.4.14.3.
Packing an array of bytes into a single variable is just as easy: 2. Reverse the elements of a byte array and pack them into an int We can reverse the order of an array’s elements and then pack them into a single value in the following way:
If you have read this answer before, you might want to scroll down to the ADL version below. It is much improved.
First, a short and sweet version that pretty much works:
#include <iostream>
#include <type_traits>
template<typename T, typename Iterator, typename=void>
struct is_iterator_of_type: std::false_type {};
template<typename T, typename Iterator>
struct is_iterator_of_type<
T,
Iterator,
typename std::enable_if<
std::is_same<
T,
typename std::iterator_traits< Iterator >::value_type
>::value
>::type
>: std::true_type {};
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
typename std::enable_if< is_iterator_of_type<int, typename Container::iterator>::value, std::ostream& >::type
{
return stream << "int container\n";
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
typename std::enable_if< is_iterator_of_type<double, typename Container::iterator>::value, std::ostream& >::type
{
return stream << "double container\n";
}
which merely detects things that look sort of like int
and double
containers with distinct overloads. I would advise changing the implementation of operator<<
. ;)
A more proper route (thanks @Xeo) would be this adl-hack. We create an auxiliary namespace where we import begin
and end
from std
, then some template functions that do argument dependent lookup on begin
and end
(seeing the std
version if we don't have a more tightly bound one), and then use these aux::adl_begin
functions to determine if what we are passed in can be treated as a container over X:
#include <iostream>
#include <vector>
#include <type_traits>
#include <iterator>
#include <set>
template<typename T, typename Iterator, typename=void>
struct is_iterator_of_type: std::false_type {};
template<typename T, typename Iterator>
struct is_iterator_of_type<
T,
Iterator,
typename std::enable_if<
std::is_same<
T,
typename std::iterator_traits< Iterator >::value_type
>::value
>::type
>: std::true_type {};
namespace aux {
using std::begin;
using std::end;
template<class T>
auto adl_begin(T&& v) -> decltype(begin(std::forward<T>(v))); // no implementation
template<class T>
auto adl_end(T&& v) -> decltype(end(std::forward<T>(v))); // no implementation
}
template<typename T, typename Container, typename=void>
struct is_container_of_type: std::false_type {};
template<typename T, typename Container>
struct is_container_of_type<
T,
Container,
typename std::enable_if<
// we only want this to be used if we iterable over doubles:
is_iterator_of_type<
T,
decltype(void(aux::adl_begin(*(Container*)nullptr)), aux::adl_end(*(Container*)nullptr)) // ensure being and end work as bonus
>::value
>::type
>: std::true_type
{};
template<class Ch, class Tr, class Container>
auto operator<<( std::basic_ostream<Ch,Tr>& stream, Container const& c ) ->
typename std::enable_if<
is_container_of_type<double, Container>::value,
decltype(stream)
>::type
{
stream << "'double' container: [ ";
for(auto&& e:c)
stream << e << " ";
return stream << "]";
}
int main() {
std::cout << std::vector<double>{1,2,3} << "\n";
std::cout << std::set<double>{3.14,2.7,-10} << "\n";
double array[] = {2.5, 3.14, 5.0};
std::cout << array << "\n";
}
With this, not only do arrays of double
s count as containers over double
, so does anything where in its namespace you define a begin
and end
function that returns iterators over double that takes the container as an argument also works. This matches how for(auto&& i:container)
lookup works (perfectly? reasonably well?), so is a good working definition of "container".
Note, however, that as we add more of these embellishments, fewer current compilers have all of the C++11 features we are using. The above compiles in gcc 4.6 I believe, but not gcc 4.5.*.
...
And here is the original short code with some testing framework around it: (useful if your compiler throws it up, you can see where it goes wrong below)
#include <iostream>
#include <type_traits>
#include <vector>
#include <iostream>
#include <set>
template<typename T, typename Iterator, typename=void>
struct is_iterator_of_type: std::false_type {};
template<typename T, typename Iterator>
struct is_iterator_of_type<
T,
Iterator,
typename std::enable_if<
std::is_same<
T,
typename std::iterator_traits< Iterator >::value_type
>::value
>::type
>: std::true_type {};
void test1() {
std::cout << is_iterator_of_type<int, std::vector<int>::iterator>::value << "\n";
}
template<typename T, typename Container>
auto foo(Container const&) -> typename std::enable_if< is_iterator_of_type<T, typename Container::iterator>::value >::type
{
std::cout << "Container of int\n";
}
template<typename T>
void foo(...)
{
std::cout << "No match\n";
}
void test2() {
std::vector<int> test;
foo<int>(test);
foo<int>(test.begin());
foo<int>(std::set<int>());
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
typename std::enable_if< is_iterator_of_type<int, typename Container::iterator>::value, std::ostream& >::type
{
return stream << "int container\n";
}
void test3() {
std::vector<int> test;
std::cout << test;
std::set<int> bar;
std::cout << bar;
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
typename std::enable_if< is_iterator_of_type<double, typename Container::iterator>::value, std::ostream& >::type
{
return stream << "double container\n";
}
void test4() {
std::vector<int> test;
std::cout << test;
std::set<int> bar;
std::cout << bar;
std::vector<double> dtest;
std::cout << dtest;
}
void test5() {
std::vector<bool> test;
// does not compile (naturally):
// std::cout << test;
}
template<typename Container>
auto operator<<( std::ostream& stream, Container const& c ) ->
typename std::enable_if< is_iterator_of_type<bool, typename Container::iterator>::value, std::ostream& >::type
{
return stream << "bool container\n";
}
void test6() {
std::vector<bool> test;
// now compiles:
std::cout << test;
}
int main() {
test1();
test2();
test3();
test4();
test5();
test6();
}
about half of the above is testing boilerplate. The is_iterator_of_type
template, and the operator<<
overloads are what you want.
I am presuming that a container of type T
is any class with a typedef iterator
which whose value_type
is a T
. This will cover every std
container, and most custom ones.
Link to execution run: http://ideone.com/lMUF4i -- note that some compilers don't support full C++11 SFINAE, and may require tomfoolery to get it to work.
Test cases left in to help someone check what level of support their compiler has for these techniques.
template<template<class T, class A> class container>
std::ostream& opertaor << ( std::ostream&, const container<X, std::allocator<X> > &)
{
}
This won't work if on your implementation vector, list, etc. have more than 2 template parameters.
Simple if not elegant - and the next person to maintain your code might appreciate a lack of fancy templates! In practice I would hide the 'Print' method in a cpp, or at least a Detail
namespace.
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <set>
#include <multiset>
class X {};
template <typename T>
std::ostream& Print(std::ostream& os, const T& container)
{
for(auto ii = container.cbegin(); ii != container.cend(); ++ii);
//etc
//
return os;
}
std::ostream& operator<<(std::ostream& os, const std::vector<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::deque<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::list<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::set<X>& v) { return Print(os, v); }
std::ostream& operator<<(std::ostream& os, const std::multiset<X>& v) { return Print(os, v); }
int main()
{
// Example
std::vector<X> v;
std::cout << v;
}
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