5

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;
}
Constructor
  • 7,273
  • 2
  • 24
  • 66
thor
  • 21,418
  • 31
  • 87
  • 173
  • You're using [reserved identifiers](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) and shouldn't overload operators for standard types. – chris Jul 24 '14 at 00:25
  • Since `tie` returns a temporary, you probably want your overload to take an rvalue reference. – Kerrek SB Jul 24 '14 at 00:26
  • 3
    Here's [a not overly complicated solution](http://ideone.com/RYPOGw). – Kerrek SB Jul 24 '14 at 00:34
  • @KerrekSB Thanks. But your solution does not seem to work for `std::tie(std::cout, std::cout) << "Foo\n" << 1 << std::endl;`, probably because `operator<<` returns `void`. – thor Jul 24 '14 at 00:59
  • @TingL: Sure. Adding `return`s everywhere would have made it not fit in the margins, but that's easily added. (Make sure to return an xvalue, though.) – Kerrek SB Jul 24 '14 at 08:33
  • @KerrekSB I think, to return properly, `t` in `printer::print(t, std::forward(x));` needs to be replaced with the tail of `t` (i.e., something like `tuple_tail(t)`) as well. That's what caught me. – thor Jul 24 '14 at 11:48
  • @TingL: I don't think that's necessary. Just return the whole tuple. – Kerrek SB Jul 24 '14 at 12:03
  • @KerrekSB, You are exactly right. I've tried returning the whole tuple and use both right and left references for `operator<<`, as in the update. If you make your comment into an answer. I will accept it. – thor Jul 24 '14 at 12:28

2 Answers2

1

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    I downvoted you because you don't explain what the problem was with the OPs code or how your code fixes it. – Puppy Jul 24 '14 at 09:46
  • @Jarod42, +1: Thanks a lot. Your code solves the original problem and corrected errors in my prototype example. But it does not work on `std::endl` or a `ofstream`. Please see my update. – thor Jul 24 '14 at 10:10
  • @TingL: for `std::endl`, it is normal, it is a template function, and it doesn't take a tuple as argument (see http://en.cppreference.com/w/cpp/io/manip/endl). – Jarod42 Jul 24 '14 at 11:28
  • For `ofstream`, I find the limitation in my code... `ofs << x` returns `ostream` (without *f*). I update my code to fix that. – Jarod42 Jul 24 '14 at 11:59
0

One problem lies with the argument: std::tuple<_TElements...>& _s.
Since you're attempting to use operator << with temporary values returned by std::tie and std::make_tuple, you're getting a compiler error because you can't bind the temporary to a reference.

A const reference (std::tuple<_TElements...> const& _s) would work though.
(You can use an rvalue reference too)

And don't use names that start with an underscore (_); those are reserved to the implementation.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
user123
  • 8,970
  • 2
  • 31
  • 52
  • 1
    The reserved identifier rules are more complicated than that. For example, `_s` is not being used inappropriately. – chris Jul 24 '14 at 00:38
  • True, and that complexity should be a good reason to simply avoid underscores in the beginning of identifiers altogether imo. I mean, they don't really give you anything =\ – user123 Jul 24 '14 at 00:40
  • True, I think there's maybe one specific place I might use one. – chris Jul 24 '14 at 00:42