-1

I'm playing with variadic templates and I'm currently trying to implement operator<< for tuple.

I've tried the following code but it doesn't compile (GCC 4.9 with -std=c++11).

template<int I, typename ... Tlist>
void print(ostream& s, tuple<Tlist...>& t)
{
    s << get<I>(t) << ", ";
    if(I < sizeof...(Tlist)){
        print<I+1>(s,t);
    }
}
template<typename ... Tlist>
ostream& operator<<(ostream& s, tuple<Tlist...> t)
{
    print<0>(s,t);
    return s;
}

The error message is very cryptic and long, but it basically says that there is no matching function call for get. Can someone explain to me why? Thanks.

EDIT: Here is the template instantiation I'm using

auto t = make_tuple(5,6,true,"aaa");
cout << t << endl;
hynner
  • 1,352
  • 1
  • 11
  • 20

2 Answers2

1

Code in an if (blah) { block } is compiled and must be valid even if the condition blah is false.

template<bool b>
using bool_t = std::integral_constant<bool, b>;

template<int I, typename ... Tlist>
void print(std::ostream& s, std::tuple<Tlist...> const& t, std::false_type) {
  // no more printing
}

template<int I, typename ... Tlist>
void print(std::ostream& s, std::tuple<Tlist...> const& t, std::true_type) {
  s << std::get<I>(t) << ", ";
  print<I+1>(s, t, bool_t<((I+1) < sizeof...(Tlist))>{});
}
template<typename ... Tlist>
std::ostream& operator<<(std::ostream& s, std::tuple<Tlist...> const& t)
{
  print<0>(s,t, bool_t<(0 < sizeof...(Tlist))>{});
  return s;
}

should work. Here we use tag dispatching to control which overload we recursively call: the 3rd argument is true_type if I is a valid index for the tuple, and false_type if not. We do this instead of an if statement. We always recurse, but when we reach the end of the tuple, we recurse into the terminating overload.

live example

As an aside, it is ambiguous if overloading << for two types defined in std is compliant with the standard: it depends if std::tuple<int> is a "user defined type" or not, a clause that the standard does not define.

On top of that, it is considered best practice to overload operators for a type within the namespace of that type, so it can be found via ADL. But, overloading << inside std is illegal under the standard (you cannot inject new overloads into std). The result can be somewhat surprising behavior in some cases, where the wrong overload is found, or an overload is not found.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

You have to use specialization or SFINAE as the branch even if it is not taken generates the instantiation:

template<int I, typename ... Tlist>
void print(ostream& s, tuple<Tlist...>& t)
{
    s << get<I>(t) << ", ";
    if(I < sizeof...(Tlist)){
        print<I+1>(s,t); // Generated even if I >= sizeof...(Tlist)
    }
}

And so you would have infinite instantiation of print if get<sizeof...(Tlist)> doesn't produce an error sooner.

You may wrote it without recursion with:

template<std::size_t ... Is, typename Tuple>
void print_helper(std::ostream& s, const Tuple& t, std::index_sequence<Is...>)
{
    int dummy[] = { 0, ((s << std::get<Is>(t) << ", "), 0)...};
    (void) dummy; // remove warning for unused var
}


template<typename Tuple>
void print(std::ostream& s, const Tuple& t)
{
    print_helper(s, t, std::make_index_sequence<std::tuple_size<Tuple>::value>());
}

Live example

Jarod42
  • 203,559
  • 14
  • 181
  • 302