Can someone please explain the "indices trick"?
Asked Answered
A

1

19

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?

Attractive answered 16/7, 2015 at 19:55 Comment(0)
A
23

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;
Asdic answered 16/7, 2015 at 20:13 Comment(10)
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 tuples just fine.)Colophony
Great answer, as usual. I found a possible definition of Bare in this answer, which seems to be something like decay_t.Kirbee
In the definition of call_f_detail, don't you mean f(std::get<Indices>(tuple)...) rather than f(std::get<Indices>(tuple))... ?Attractive
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?Attractive
@Attractive 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 on expanding different pack expressionsAsdic
I believe it's correct as written. f(std::get<Indices>(tuple)...) expands to f(42, 'x', 3.14) with the sample tuple mentioned in the answer, but f(std::get<Indices>(tuple))... gets expanded to f(42), f('x'), f(3.14)Crying
@Attractive I don't understand your other question. What redundancy?Asdic
(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`?Attractive
@Attractive (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?Asdic
(2) How very unconfusing. Not. :-(Attractive

© 2022 - 2024 — McMap. All rights reserved.