Вопрос здесь, какой будет тип возвращаемого типа, если это будет возможно? Это должно быть известно во время компиляции, но кортеж может содержать элементы разных типов.
Предположим, у нас есть кортеж из трех элементов:
auto tuple = std::make_tuple(10, "", A());
using tuple_type = decltype(tuple);
По-видимому, получение N-го элемента не имеет особого смысла. Какой это будет тип? Это не известно до времени выполнения. Однако вместо получения N-го элемента вы можете применить к нему функцию, учитывая, что все элементы поддерживают некоторый общий протокол:
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));
}
Этот код "динамически" обрабатывает элемент, учитывая индекс n
. Общий протокол в этом примере - функция func
, которая может сделать что-то осмысленное со всеми возможными типами, используемыми в кортеже.
Однако написание такого кода вручную утомительно, мы хотим сделать его более общим. Давайте начнем с извлечения функции приложения, чтобы мы могли повторно использовать одну и ту же функцию process
для различных функторов:
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));
}
}
В этом случае F
может быть реализовано как что-то вроде:
// Prints any printable type to the stdout
struct printer
{
static void apply(E e)
{
std::cout << e << std::endl;
}
}
Давайте создадим компилятор для генерации всего этого кода, давайте сделаем его универсальным:
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.
}
};
Использование:
wrapper<0>::template apply_to<printer>(tuple, 2);
Сделать его полностью общим - это еще одна история. По крайней мере, он должен быть независимым от типа кортежа. Затем вы, вероятно, захотите сгенерировать возвращаемый тип функтора, чтобы вы могли вернуть значимый результат. В-третьих, заставить функтор принимать дополнительные параметры.
P.S. Я не настоящий разработчик C ++, поэтому подход выше может быть полное отсутствие. Тем не менее, я нашел это полезным для моего проекта микроконтроллера, где я хочу, чтобы как можно больше было разрешено во время компиляции и все же было достаточно универсальным, чтобы я мог легко перемешать вещи. Например, «меню» в моем проекте - это, по сути, кортеж «действий», в котором каждое действие представляет собой отдельный класс, который поддерживает простой протокол, такой как «печать этикетки на текущей позиции на ЖК-дисплее» и «активация и запуск цикла пользовательского интерфейса». .