I am trying to write an stream operator <<
that can output to a std::tuple
of streams instead of one stream. So basically, I am trying to write the Unix tee
command in c++, and do:
std::tie(std::cout,std::clog) << 1;
I tried to write the stream operator recursively using variadic template programming in c++11. What I have so far is as in the code below. But the code doesn't compile, and the error message is quite long.
My question is, how to fix the code to make it work?
The first error message from compiling with g++ -std=c++11
(gcc-4.8.1) was:
test.cpp:24:33: error: no match for 'operator<<' (operand types are 'std::tuple<std::basic_ostream<char, std::char_traits<char> >&, std::basic_ostream<char, std::char_traits<char> >&>' and 'int')
std::tie(std::cout,std::cout) << 1;
P.S. I searched SO, and there is some posts about writing a composite stream object. The code involves a lot of internals of stream and streambuf. What I seek here is a simple/naive solution in a dozen of lines to accomplish a similar task.
Thanks
My code:
#include <iostream>
#include <tuple>
template<typename _Value, typename Head, typename ... Tail>
struct _tee_stream {
static std::tuple<Head,Tail...>& _print(std::tuple<Head,Tail...>& _s, const _Value& _t) {
return std::make_tuple(std::get<0>(_s) << _t ,_tee_stream<Tail...,_Value>::_print(_s,_t));
}
};
template<typename _Value>
struct _tee_stream<_Value, std::tuple<>> {
static std::tuple<>& _print(std::tuple<>& _s, const _Value& _t) {
return _s;
}
};
template< typename _Value, typename... _TElements>
std::tuple<_TElements...>& operator<<(std::tuple<_TElements...>& _s, const _Value& _t) {
return _tee_stream<_Value, std::tuple<_TElements...>>::_print(_s, _t);
};
int main() {
std::tie(std::cout,std::cout) << 1; //no compile
std::make_tuple(std::cout,std::cout) << 1; //no compile either
}
UPDATE: below is what worked for me based on @KerrekSB's example code:
#include <iostream>
#include <tuple>
#include <utility>
#include <fstream>
template <unsigned int N>
struct tee_stream
{
template <typename ...Args, typename T>
static std::tuple<Args...> & print(std::tuple<Args...> & t, T && x)
{
std::get<sizeof...(Args) - N>(t) << x;
tee_stream<N - 1>::print(t, std::forward<T>(x));
return t;
}
};
template <>
struct tee_stream<0>
{
template <typename ...Args, typename T>
static std::tuple<Args...> & print(std::tuple<Args...> &, T &&) {}
};
template <typename ...Args, typename T>
std::tuple<Args...> & operator<<(std::tuple<Args...> & t, T && x)
{
return tee_stream<sizeof...(Args)>::print(t, std::forward<T>(x));
}
template <typename ...Args, typename T>
std::tuple<Args...> & operator<<(std::tuple<Args...> && t, T && x)
{
return tee_stream<sizeof...(Args)>::print(t, std::forward<T>(x));
}
int main()
{
std::ofstream os("a.txt");
auto t = std::tie(std::cout, os);
t << "Foo" << "Bar\n";
std::tie(std::cout, os) << "Foo" << "Bar\n";
}
http://ideone.com/fBXuvP
@Jarod42,
Thanks a lot. Your code does the recursion/looping much more compactly than my prototype, and fixes the use of reference types in my code. The original test cases work as your demo showed. However, I still can't get your version compile if I use std::endl
(instead of "Foo") or std::ofstream
(instead of std::cout
) in the stream, as shown below in the "not OK" lines. Any idea how to fix those as well?
#include <iostream>
#include <fstream>
#include <tuple>
#if 1 // Not in C++11 // make_index_sequence
#include <cstdint>
template <std::size_t...> struct index_sequence {};
template <std::size_t N, std::size_t... Is>
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> {};
template <std::size_t... Is>
struct make_index_sequence<0u, Is...> : index_sequence<Is...> {};
#endif // make_index_sequence
namespace detail
{
template <typename Tuple, typename T, std::size_t...Is>
Tuple output(const Tuple& t, const T& x, index_sequence<Is...>) {
return Tuple{(std::get<Is>(t) << x)...};
}
}
template <typename ...Args, typename T>
std::tuple<Args&...> operator<<(const std::tuple<Args&...>& t, const T& x) {
return detail::output(t, x, make_index_sequence<sizeof...(Args)>());
}
int main() {
std::ofstream os("aa.txt");
os << "Hi" << std::endl;
std::tie(std::cout, std::cout) << "Foo" << "Bar"; //OK
std::tie(std::cout, std::cout) << "Foo" << "Bar" << std::endl; //not OK on endl
std::tie(std::cout, os) << 1 << "Foo" << "Bar"; //not OK on ofstream
return 0;
}
You may use the following:
#if 1 // Not in C++11 // make_index_sequence
#include <cstdint>
template <std::size_t...> struct index_sequence {};
template <std::size_t N, std::size_t... Is>
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> {};
template <std::size_t... Is>
struct make_index_sequence<0u, Is...> : index_sequence<Is...> {};
#endif // make_index_sequence
// Helper. C++11 cannot use: auto f() { return foo(); }
// Usage: auto f() -> Return(foo())
#define Return(ret) decltype(ret) { return ret; }
namespace detail
{
template <typename Tuple, typename T, std::size_t...Is>
auto output(const Tuple& t, const T& x, index_sequence<Is...>) ->
Return (std::tie(std::get<Is>(t) << x...))
}
template <typename ...Args, typename T>
auto operator<<(const std::tuple<Args&...>& t, const T& x) ->
Return(detail::output(t, x, make_index_sequence<sizeof...(Args)>()))
live example
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