Search code examples
c++c++17variadic-templates

Unpacking parameters when returning a proxy class for stream operator


I am trying to achieve the following behavior for the usage of stream operator:

std::cout << print(42, "foo", 'c', 2.0);

This is the implementation of the stream operator proxy class:

template <typename T>
class StreamProxy {
  std::function<std::ostream &(std::ostream &, T)> m_func;
  T m_arg;

 public:
  StreamProxy(std::function<std::ostream &(std::ostream &, T)> f, T arg)
      : m_func(f), m_arg(arg) {}
  inline void do_op(std::ostream &str) const { m_func(str, m_arg); }
};

template <typename T>
inline std::ostream &operator<<(std::ostream &os, StreamProxy<T> const &m) {
  if (!os.good()) return os;
  m.do_op(os);
  return os;
}

template <typename T>
class Print : public StreamProxy<T const &> {
  inline static std::ostream &call(std::ostream &os, T const &v) {
    os << v;
    return os;
  }

 public:
  inline Print(T const &v) : StreamProxy<T const &>(call, v) {}
};

So far, I only got the base case working and I am struggling with unpacking the parameters:

template <typename T>
inline auto print(T const &v) {
  return Print<T>(v);
}

template <typename T, typename... Args>
inline auto print(T const &v, Args &&... args) {

  // NOTE: As pointed out by  Davis Herring, the usage of stream operator here doesn't make sense.
  return print(v) << print(args...); // This will fail!
}

How can this be implemented using parameter pack? The error message below seems rather misleading.

/home/me/Projects/ostr/include/ostr.hpp:62: error: no match for ‘operator<<’ (operand types are ‘Print<char [14]>’ and ‘Print<int>’)
   return print(v) << print(args...);
          ~~~~~~~~~^~~~~~~~~~~~~~~~~

Solution

  • It seems to me that you've over-complicating the problem and that you've placed the variadic unpack in the wrong place (with stream operator where stream isn't available).

    It seems to me that is simpler capturing the arguments in a lambda function and calling the function with a std::ostream.

    I mean... something as follows

    #include <iostream>
    #include <functional>
    
    struct foo
     {
       std::function<std::ostream & (std::ostream &)> l;
    
       template <typename ... As>
       foo (As && ... as)
         : l{[&as...](std::ostream & os) -> std::ostream &
             { return ((os << std::forward<As>(as)), ...); }}
        { } 
     };
    
    std::ostream & operator<< (std::ostream &os, foo const & f)
     { return os.good() ? f.l(os) : os; }
    
    template <typename ... As>
    foo print (As &&... as)
     { return { std::forward<As>(as)... }; }
    
    int main ()
     {
       std::cout << print(42, "foo", 'c', 2.0);
     }