Is there a legal way to print tuples and pairs using operator<<?
Asked Answered
F

3

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

Freyah answered 22/2, 2011 at 9:16 Comment(0)
G
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
Gewirtz answered 22/2, 2011 at 9:58 Comment(5)
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***ersFreyah
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.Macbeth
@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<string>(streamable(t) etcCeric
You don't need to rewrite the tuple class. You stream like this std::cout << makeFormatTuple( tup ); where tup is your tuple.Gewirtz
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.Hexylresorcinol
M
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);
}
}
Macbeth answered 22/2, 2011 at 9:42 Comment(9)
Except that doesn't work. Since tuple is in std ADL for cout<<my_tuple<<endl; will look ONLY in std AFAIK.Freyah
@KitsuneYMG: It does work; ADL also looks in the scope(s) of the calling function. codepad.org/anO76D0MMacbeth
@Fred Are you saying that I'd have to declare the operator<< in every scope I want to use it in?Freyah
@KitsuneYMG: No. Your projects use a single root namespace, 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.Macbeth
The drawback is still that it won't work in case std::operator<< is expected (e.g boost::lexical_cast).Ceric
@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.Macbeth
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?Hexylresorcinol
@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.Macbeth
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: ideone.com/DEK2FHexylresorcinol
G
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
Gewirtz answered 22/2, 2011 at 9:58 Comment(5)
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***ersFreyah
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.Macbeth
@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<string>(streamable(t) etcCeric
You don't need to rewrite the tuple class. You stream like this std::cout << makeFormatTuple( tup ); where tup is your tuple.Gewirtz
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.Hexylresorcinol
C
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.

Carry answered 7/3, 2021 at 11:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.