The question here, what would be the type return type if that would be possible? It has to be known at compile time, but tuple may contain elements of different types.
Let's assume we have a tuple of three elements:
auto tuple = std::make_tuple(10, "", A());
using tuple_type = decltype(tuple);
Apparently, getting N-th element doesn't make much sense. What type would it be? It's not known until runtime. However, rather than getting N-th element you can apply a function to it, given that all elements support some common protocol:
void process(int n)
{
if (n == 0)
func(std::get<0>(tuple));
else if (n == 1)
func(std::get<1>(tuple));
else if (n == 2)
func(std::get<2>(tuple));
}
This code "dynamically" processes element, given the index n
. The common protocol in this example is function func
which can do something meaningful with all possible types used in the tuple.
However, writing such code by hand is tedious, we want to make it more generic. Let's start with extracting the application function, so we can reuse same process
function for different functors:
template<template<typename > class F>
void process(int n)
{
if (n == 0)
{
using E = typename std::tuple_element<0, tuple_type>::type;
F<E>::apply(std::get<0>(tuple));
}
else if (n == 1)
{
using E = typename std::tuple_element<1, tuple_type>::type;
F<E>::apply(std::get<1>(tuple));
}
else if (n == 2)
{
using E = typename std::tuple_element<2, tuple_type>::type;
F<E>::apply(std::get<2>(tuple));
}
}
In this case F
could be implemented as something like:
// Prints any printable type to the stdout
struct printer
{
static void apply(E e)
{
std::cout << e << std::endl;
}
}
Let's make compiler to generate all of that code, let's make it generic:
constexpr static std::size_t arity = std::tuple_size<tuple_type>::value;
template<int N>
struct wrapper
{
template<template<typename, typename ... > class F>
static void apply_to(tuple_type& tuple, int idx)
{
if (idx)
// Double recursion: compile and runtime.
// Compile-time "recursion" will be terminated once
// we reach condition N == tuple arity
// Runtime recursion terminates once idx is zero.
wrapper<N + 1>::template apply_to<F>(tuple, idx - 1);
else
{
// idx == 0 (which means original index is equal to N).
using E = typename std::tuple_element<N, tuple_type>::type;
F<E>::apply(std::get<N>(tuple));
}
}
};
// Termination condition: N == arity.
template<>
struct wrapper<arity>
{
template<template<typename, typename ... > class F>
static void apply_to(tuple_type&, int)
{
// Throw exception or something. Index is too big.
}
};
Usage:
wrapper<0>::template apply_to<printer>(tuple, 2);
Making it completely generic is another story, though. At least it needs to be independent of the tuple type. Then, you probably want to generify return type of the functor, so you can return meaningful result. Third, making functor to accept extra parameters.
P.S. I am not real C++ developer, so the approach above could be total nonsence. However, I found it useful for my microcontroller project where I want as much as possible to be resolved at compile time and yet be generic enough, so I can shuffle things around easily. For example, a "menu" in my project is basically a tuple of "actions", there each action is a separate class which supports simple protocol like "print your label at current position on LCD" and "activate and run your UI loop".