9

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]'

KitsuneYMG
  • 12,753
  • 4
  • 37
  • 58

3 Answers3

2

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);
}
}
Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
  • Except that doesn't work. Since `tuple` is in `std` ADL for `cout< – KitsuneYMG Feb 22 '11 at 09:48
  • @KitsuneYMG: It does work; ADL also looks in the scope(s) of the calling function. http://codepad.org/anO76D0M – Fred Nurk Feb 22 '11 at 09:49
  • @Fred Are you saying that I'd have to declare the `operator<<` in every scope I want to use it in? – KitsuneYMG Feb 22 '11 at 09:56
  • @KitsuneYMG: No. Your projects use a [single root namespace](http://programmers.stackexchange.com/questions/50120/best-practices-for-using-namespaces-in-c/50148#50148), right? Declare them once, in that namespace, then anything in your project can use them. And you won't conflict with any other project/library you use or that uses your code. – Fred Nurk Feb 22 '11 at 10:00
  • 2
    The drawback is still that it won't work in case `std::operator<<` is expected (e.g `boost::lexical_cast`). – visitor Feb 22 '11 at 12:03
  • @visitor: That's by design and very much on purpose. Anything you write which could be used there can also be written by someone else, and, just like violating the ODR as in my answer, you'd have unresolvable ambiguity. – Fred Nurk Feb 22 '11 at 13:45
  • Not sure I follow. Anyone can write a `kitsune_ymg::tuples::operator<<` and have an unresolvable ambiguity? - The answer below works nice with `lexical_cast`. Where is the unresolvable ambiguity? – UncleBens Feb 22 '11 at 23:17
  • 1
    @UncleBens: KitsuneYMG can reasonably expect to control kitsune_ymb. No one but the implementation can expect to control std. The other answer does not add declarations to namespace std, nor does it work with nested tuples and other cases. – Fred Nurk Feb 22 '11 at 23:30
  • Basically it is entirely possible to write a function `streamable(tuple)`, such that you can send the result to a stream and have it also handle nested tuples. Combined from code on this page: http://ideone.com/DEK2F – UncleBens Feb 23 '11 at 08:21
2

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
UncleBens
  • 40,819
  • 6
  • 57
  • 90
CashCow
  • 30,981
  • 5
  • 61
  • 92
  • I have tried this too and you do need explicit casts even with implicit constructors. The logical extension of this answer is to completely replace all uses of `std::tuple`, `std::make_tuple`, etc with my own classes. I really just wanted to give people an easy, non-intrusive way to print the f***ers – KitsuneYMG Feb 22 '11 at 10:06
  • 2
    Do NOT rewrite your own tuple types, even as light wrappers, just to format them for output. But even if you did, this won't work for existing types. – Fred Nurk Feb 22 '11 at 10:07
  • @Fred: As far as I can see, this would work very nice, even with `boost::lexical_cast`. Perhaps the function could have a better name (it does *not* make a tuple), e.g `std::cout << repr(t);` or `lexical_cast(streamable(t)` etc – visitor Feb 22 '11 at 12:08
  • You don't need to rewrite the tuple class. You stream like this `std::cout << makeFormatTuple( tup );` where `tup` is your tuple. – CashCow Feb 23 '11 at 09:58
  • Fixed syntax errors with variadic templates. Anyway, I don't see what Fred has against it. His answer is that you can't have and **shouldn't dream of** `operator<<` being found by ADL for tuples. This, IMO, is a perfectly acceptable next best thing one can do. – UncleBens Feb 23 '11 at 15:32
0

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.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103