8

It's been a while that I've been doing C++ but I'm not familiar with templates.

Recently, I tried to write a class that wrap a std::vector<std::tuple<Types...>>. This class must have member functions, and I really need to be able to iterate over the tuple. In fact, if I am able to print every element of a tuple (in the order), I would be able to do everything I need.

I found a solution using a cast, but I'm not really confident with it since it is based on a cast that I don't really like (plus, when I try to use static_cast, it doesn't compile anymore).

My question is, is the following code correct, portable, is it a hack and should I find another way to do this than to use this cast ? Also, this cast is probably a runtime-cast right ? Is there a way to do what I want without this ?

std::ostream& operator<<(std::ostream& out, std::tuple<> const& tuple)
{
    return out; // Nothing to do here
}

template<typename First, typename... Types>
std::ostream& operator<<(std::ostream& out, std::tuple<First, Types...> const& tuple)
{
    out << std::get<0>(tuple) << " ";

    // The cast that I don't like
    return out << (std::tuple<Types...>&) tuple;
}

int main()
{
    auto tuple = std::make_tuple(1, 2.3, "Hello");
    std::cout << tuple << std::endl;
    return 0;
}

Thank you in advance for your answers.

tforgione
  • 1,234
  • 15
  • 29

2 Answers2

11

Use std::index_sequence_for for fun and profit.

template <typename TupleLike, size_t ... Inds>
std::ostream& PrintHelper(std::ostream& out, TupleLike const& tuple, std::index_sequence<Inds...>)
{
  int unused[] = {0, (void(out << std::get<Inds>(tuple) << " "), 0)...};
  (void)unused;
  return out;
}

template<typename... Types>
std::ostream& operator<<(std::ostream& out, std::tuple<Types...> const& tuple)
{
  return PrintHelper(out, tuple, std::index_sequence_for<Types...>());
}

EDIT : Live Demo. Thanks to @dyp. This uses an expansion trick from this answer.

Community
  • 1
  • 1
Pradhan
  • 16,391
  • 3
  • 44
  • 59
0

I found another way to do what I want. I used this article that can print the elements of a tuple in the descending order, and I use a second index J == std::tuple_size<std::tuple<Types...>>::value - I so I can specialize the template when I==0.

template<std::size_t I, std::size_t J, typename... Types>
struct printHelper
{
    std::ostream& operator()(std::ostream& out, std::tuple<Types...> const& tuple)
    {
        out << std::get<J>(tuple) << " ";

        // Recursive call without cast
        return printHelper<I-1,J+1,Types...>{}(out, tuple);
    };
};

// Specialization on the last element when I==0
template<std::size_t J, typename... Types>
struct printHelper<0,J,Types...>
{
    std::ostream& operator()(std::ostream& out, std::tuple<Types...> const& tuple)
    {
        // Nothing to do here
        return out;
    }
};

template<typename... Types>
std::ostream& operator<<(std::ostream& out, std::tuple<Types...> const& tuple)
{
    return printHelper<std::tuple_size<std::tuple<Types...>>::value, 0, Types...>{}(out, tuple);
}
tforgione
  • 1,234
  • 15
  • 29