16

I noticed the "indices trick" being mentioned in the context of pretty-printing tuples. It sounded interesting, so I followed the link.

Well, that did not go well. I understood the question, but could really not follow what was going on. Why do we even need indices of anything? How do the different functions defined there help us? What is 'Bare'? etc.

Can someone give a play-by-play of that thing for the less-than-experts on parameter packs and variadic tuples?

T.C.
  • 133,968
  • 17
  • 288
  • 421
einpoklum
  • 118,144
  • 57
  • 340
  • 684

1 Answers1

21

The problem is: we have a std::tuple<T1, T2, ...> and we have some function f that we can to call on each element, where f returns an int, and we want to store those results in an array.

Let's start with a concrete case:

template <typename T> int f(T ) { return sizeof(T); }

std::tuple<int, char, double> tup{42, 'x', 3.14};
std::array<int, 3> arr{ f(std::get<0>(tup)), 
                        f(std::get<1>(tup)),
                        f(std::get<2>(tup)) );

Except writing out all those gets is inconvenient and redundant at best, error-prone at worst.

First we need to include the utility header for std::index_sequence and std::make_index_sequence:

#include <utility>

Now, let's say we had a type index_sequence<0, 1, 2>. We could use that to collapse that array initialization into a variadic pack expansion:

template <typename Tuple, size_t... Indices>
std::array<int, sizeof...(Indices)> 
call_f_detail(Tuple& tuple, std::index_sequence<Indices...> ) {
    return { f(std::get<Indices>(tuple))... };
}

That's because within the function, f(std::get<Indices>(tuple))... gets expanded to f(std::get<0>(tuple)), f(std::get<1>(tuple)), f(std::get<2>(tuple)). Which is exactly what we want.

The last detail of the problem is just generating that particular index sequence. C++14 actually gives us such a utility named make_index_sequence

template <typename Tuple>
std::array<int, std::tuple_size<Tuple>::value>
call_f(Tuple& tuple) {
    return call_f_detail(tuple,
        // make the sequence type sequence<0, 1, 2, ..., N-1>
        std::make_index_sequence<std::tuple_size<Tuple>::value>{}
        );
}

whereas the article you linked simply explains how one might implement such a metafunction.

Bare is probably something like, from Luc Danton's answer:

template<typename T>
using Bare = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
CodeMonkey
  • 4,067
  • 1
  • 31
  • 43
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    `Bare` seems to something along the lines of `remove_reference_t`. Note that the code on the page OP linked takes the tuple by forwarding reference, so `Tuple` can be a reference type, and `tuple_size` doesn't work on reference types. (Technically, the `remove_cv` isn't needed. `tuple_size` is supposed to work on cv-qualified `tuple`s just fine.) – T.C. Jul 16 '15 at 20:23
  • 1
    Great answer, as usual. I found a possible definition of `Bare` in [this answer](http://stackoverflow.com/a/10615119/1274223), which seems to be something like `decay_t`. – Alejandro Jul 16 '15 at 20:23
  • In the definition of call_f_detail, don't you mean `f(std::get(tuple)...)` rather than `f(std::get(tuple))...` ? – einpoklum Jul 16 '15 at 20:44
  • Also, a more significant question - why do we need the flexibility of 'supporting' any sequence of indices? Doesn't indices<0, 1, 2> incorporate unnecessary redundancy? – einpoklum Jul 16 '15 at 20:45
  • 3
    @einpoklum No I don't. Pack expansion works by taking the expression to the left of the `...`, so the first expression would expect into `f(std::get<0>(tuple), std::get<1>(tuple), ...)` instead of `f(std::get<0>(tuple)), f(std::get<1>(tuple)), ...`. We want to call `f` on each element individually, not on all of them together. See [my answer here](http://stackoverflow.com/a/26767333/2069064) on expanding different pack expressions – Barry Jul 16 '15 at 20:46
  • I believe it's correct as written. `f(std::get(tuple)...)` expands to `f(42, 'x', 3.14)` with the sample tuple mentioned in the answer, but `f(std::get(tuple))...` gets expanded to `f(42), f('x'), f(3.14)` – celticminstrel Jul 16 '15 at 20:47
  • @einpoklum I don't understand your other question. What redundancy? – Barry Jul 16 '15 at 20:48
  • (1) Re redundancy: The template argument values are exactly the same as their indices in the sequence of template arguments. In regular (non-meta) programming, you would usually avoid using `size_t a[3] = { 0, 1, 2}` since it's silly. (2) So, `index_sequence` actually exists in the standard library, but not indices, or sequence, or any of the other stuff? (3) Can't we use `std::index_sequence_for` somehow`? – einpoklum Jul 16 '15 at 20:54
  • @einpoklum (1) That's why we use `make_index_sequence` to make the index_sequence for us? (2) They're all there, see the link in my answer. (3) You could, but it's implemented in terms of `make_index_sequence` anyway, so what's the difference? – Barry Jul 16 '15 at 21:02
  • (2) How very unconfusing. Not. :-( – einpoklum Jul 16 '15 at 21:03