8
votes

I have a set of templates/functions that allow me to print a tuple/pair assuming that each type in the tuple/pair has operator<< defined for it. Unfortunately, due to 17.4.3.1, it is illegal to add my operator<< overloads to std. Is there another way to get ADL to find my operator<<? If not, is there any actual harm in wrapping my overload in namespace std{}?

The code for anyone interested: (I'm using gcc-4.5)

namespace tuples {
  using ::std::tuple;
  using ::std::make_tuple;
  using ::std::get; 
namespace detail {

template< typename...args >
size_t size( tuple<args...> const& )
{
  return sizeof...(args);
};

template<size_t N>
struct for_each_ri_impl
{
  template<typename Func, typename Tuple>
  void operator()(Func func, Tuple const& arg)
  {
    for_each_ri_impl<N-1>()(func, arg );
    func( get<N>( arg ), size(arg) - N - 1 );
  }
};

template<>
struct for_each_ri_impl<0>
{
  template<typename Func, typename Tuple>
  void operator()(Func func, Tuple const& arg)
  {
    func( get<0>( arg ), size(arg) - 1 );
  }
};
}//detail

template<typename Func, typename ... Args>
void for_each_ri( tuple<Args...>const& tup, Func func )
{
  detail::for_each_ri_impl< sizeof...(Args)-1>()( func, tup );
}


struct printer {
  std::ostream& out;
  const std::string& str;
  explicit printer( std::ostream& out=std::cout, std::string const& str="," ) : out(out), str(str) { }

  template<typename T>void operator()(T const&t, size_t i=-1) const { out<<t; if(i) out<<str; }
};

//Should this next line go into namespace std? Is there another way?
template<typename ... Args>
std::ostream& operator<<(std::ostream& out, std::tuple< Args... > const& tup)
{
  out << '[';
  tuples::for_each_ri( tup, tuples::printer(out,", ") );
  return out << ']';
}

} //tuples

//Edits --
int main()
{
using namespace std;

cout<<make_tuple(1,'a',"Hello")<<endl;

return 0;
}

Compiling the above yields:

test.cpp: In function 'int main()':
test.cpp:69:31: error: cannot bind 'std::ostream' lvalue to 'std::basic_ostream&&' > /opt/local/include/gcc45/c++/ostream:579:5: error: initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits, _Tp = std::tuple]'

3

3 Answers

2
votes

Put your own light wrapper class around it and then overload operator<< to use that. However beware that even if your light wrapper has an implicit constructor you will probably still need to use it explicitly when you pass it to operator<<

    template< typename ...VA_ARGS >
    struct format_tuple
    {
       typedef tuple<VA_ARGS...> tuple_type;
    // any format variables
       const tuple_type & tup;
       format_tuple( const tuple_type& t): tup(t) {}
    };

    template< typename ...VA_ARGS > format_tuple<VA_ARGS...> makeFormatTuple( const tuple<VA_ARGS...> & t ) 
    {
       return format_tuple( t );
    }

    template<typename ...VA_ARGS>
    std::ostream& operator<<( std::ostream& os, const format_tuple<VA_ARGS...> & ft ) 
    {
      // original implementation
    }

This is an outline as I'm not sure exactly how to do it with variadic templates although it should be possible. You can easily implement several versions though with 1, 2, 3, etc.parameters, eg:

    template<typename T1, typename T2, typename T3>
    class format_tuple_3; //etc


    template<typename T1, typename T2, typename T3>
    format_tuple_3<T1, T2, T3> makeFormatTuple( tuple<T1,T2,T3> const&); //etc
2
votes

The harm is someone else (such as in a third party library you want to use) also adding these declarations to std. Even if theirs behave identically, you'll violate the ODR.

Just put these in your project's namespace:

namespace kitsune_ymg {
// Op<< overloads here.
// Your "normal" stuff.
void normal_stuff() {
  std::cout << std::pair<int, int>(42, 3);
}

And then anything in your project can use them.

I'm still not sure exactly why this doesn't work for you, but it seems you want something like:

namespace kitsune_ymg {
namespace tuples {
  // Op<< overloads here.
}
using namespace tuples;
// Your "normal" stuff.
}

namespace completely_separate_project {
using kitsune_ymg::tuples;
// Now you can use those op<< overloads in this scope, too.
void perfectly_normal_beast() {
  std::cout << std::pair<int, int>(42, 3);
}
}
0
votes

You mustn't add your own operator<< to std. However, you can write an adapter for tuples, or one for streams, and use that, with a minimal amount of change to the call sites.

I'll assume C++17 or newer (to use structured bindings and fold expressions), although the question is obviously much older.


Adapt the tuple

#include <ostream>
#include <tuple>

template<typename... Args>
struct printable_tuple
{
    typedef std::tuple<Args...> tuple_type;
    const tuple_type& t;

    // implicit converting constructor
    printable_tuple(const tuple_type& t)
        : t(t)
    {}
};

template<typename... Args>
std::ostream& operator<<(std::ostream& os, const printable_tuple<Args...>& tuple)
{
    const char *sep = "";
    os << '[';
    std::apply([&os,&sep](auto&&...args){((os << sep << args, sep = ","),...);}, tuple.t);
    return os << ']';
}
#include <iostream>
int main()
{
    std::cout << format_tuple{std::tuple{1,'a',"Hello"}} << '\n';
}

This is the least intrusive, as we can use the returned stream normally (if (os << tuple), for instance), but it requires wrapping each and every argument.


Adapt the stream

#include <tuple>

template<typename Stream>
class tuple_ostream
{
    Stream& os;
public:
    // conversions from and to Stream
    tuple_ostream(Stream& os) : os{os} {}
    operator Stream&() const { return os; };

    // generic forwarding <<
    template<typename T>
    tuple_ostream& operator<<(const T&t)
    {
        os << t;
        return *this;
    }

    // overload for tuples
    template<typename... Args>
    tuple_ostream& operator<<(const std::tuple<Args...>&t)
    {
        const char *sep = "";
        os << '[';
        std::apply([this,&sep](auto&&...args){((os << sep << args, sep = ","),...);}, t);
        os << ']';
        return *this;
    }
};
#include <iostream>
int main()
{
    tuple_ostream{std::cout} << std::tuple{1,'a',"Hello"} << '\n';
}

Adapting the stream is obviously simpler when we need to write several tuples to the same stream, but we can no longer directly use the returned stream as the original unless we add more functions to the wrapper.


Hat-tip to CashCow's answer for a starting point for this one.