c++ - tupla - ¿Cómo obtener el elemento i-th de un std:: tuple cuando no se sabe en tiempo de compilación?
tuple c++ example (4)
Tengo una variable i
de tipo std::size_t
y una tupla de tipo std::tuple
. Quiero obtener el elemento i
-th de la tupla. Intenté esto:
// bindings... is of type const T&...
auto bindings_tuple = std::make_tuple(bindings...);
auto binding = std::tuple_element<i, const T&...>(bindings_tuple);
Pero recibo este error de compilación que dice que el primer argumento de la plantilla debe ser una expresión constante constante:
error: el argumento de plantilla que no es de tipo ''
std::size_t
'' (también conocido como ''unsigned long
'') no es una expresión constante constante
¿Es posible obtener el elemento i
-ésimo de una tupla y cómo hacerlo?
Me gustaría hacer esto sin usar boost, si es posible.
Esto es posible:
struct Functor
{
template<typename T>
void operator()(T& t) const { std::cout << t << std::endl; }
};
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_index(int, std::tuple<Tp...> &, FuncT)
{ }
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_index(int index, std::tuple<Tp...>& t, FuncT f)
{
if (index == 0) f(std::get<I>(t));
for_index<I + 1, FuncT, Tp...>(index-1, t, f);
}
auto t = make_tuple(1, 2, "abc", "def", 4.0f);
int i = 2; // for example
for_index(i, t, Functor());
Este código se imprimirá:
a B C
Muestra de trabajo sobre ideone: sample
La pregunta aquí, ¿cuál sería el tipo de tipo de retorno si eso fuera posible? Debe conocerse en el momento de la compilación, pero la tupla puede contener elementos de diferentes tipos.
Supongamos que tenemos una tupla de tres elementos:
auto tuple = std::make_tuple(10, "", A());
using tuple_type = decltype(tuple);
Aparentemente, obtener el elemento N-th no tiene mucho sentido. ¿Qué tipo sería? No se sabe hasta el tiempo de ejecución. Sin embargo, en lugar de obtener el elemento N-th, puede aplicarle una función, dado que todos los elementos admiten algún protocolo comú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));
}
Este código procesa "dinámicamente" el elemento, dado el índice n
. El protocolo común en este ejemplo es la función func
que puede hacer algo significativo con todos los tipos posibles utilizados en la tupla.
Sin embargo, escribir dicho código a mano es tedioso, queremos hacerlo más genérico. Empecemos con la extracción de la función de la aplicación, para poder reutilizar la misma función de process
para diferentes funtores:
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));
}
}
En este caso F
podría implementarse como algo así como:
// Prints any printable type to the stdout
struct printer
{
static void apply(E e)
{
std::cout << e << std::endl;
}
}
Hagamos un compilador para generar todo ese código, hagámoslo genérico:
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.
}
};
Uso:
wrapper<0>::template apply_to<printer>(tuple, 2);
Sin embargo, hacerlo completamente genérico es otra historia. Al menos tiene que ser independiente del tipo de tupla. Entonces, es probable que desee generar el tipo de retorno del functor, para que pueda devolver un resultado significativo. Tercero, hacer que el funtor acepte parámetros extra.
PS No soy un verdadero desarrollador de C ++, por lo que el enfoque anterior podría ser una ausencia total. Sin embargo, lo encontré útil para mi proyecto de microcontrolador donde quiero que se resuelva todo lo posible en el momento de la compilación y, sin embargo, sea lo suficientemente genérico, por lo que puedo barajar las cosas fácilmente. Por ejemplo, un "menú" en mi proyecto es básicamente una tupla de "acciones", allí cada acción es una clase separada que admite un protocolo simple como "imprimir su etiqueta en la posición actual en la pantalla LCD" y "activar y ejecutar su bucle de IU" .
No se puede. Para eso no es una tupla. Si necesita acceso dinámico a un elemento, use std::array<T,N>
, que es casi idéntico a std::tuple<T,...,T>
pero le da el operador dinámico [i]
; o incluso un contenedor completamente dinámico como std::vector<T>
.
Probablemente, esto no es lo que quiere OP, pero de todos modos, es posible devolver el elemento i -th utilizando un tiempo de ejecución, siempre que devuelva un tipo de variante como boost::variant
o boost::any
,
#include <tuple>
#include <stdexcept>
#include <boost/variant.hpp>
template <size_t n, typename... T>
boost::variant<T...> dynamic_get_impl(size_t i, const std::tuple<T...>& tpl)
{
if (i == n)
return std::get<n>(tpl);
else if (n == sizeof...(T) - 1)
throw std::out_of_range("Tuple element out of range.");
else
return dynamic_get_impl<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
}
template <typename... T>
boost::variant<T...> dynamic_get(size_t i, const std::tuple<T...>& tpl)
{
return dynamic_get_impl<0>(i, tpl);
}
Por ejemplo:
#include <string>
#include <iostream>
int main()
{
std::tuple<int, float, std::string, int> tpl {4, 6.6, "hello", 7};
for (size_t i = 0; i < 5; ++ i)
std::cout << i << " = " << dynamic_get(i, tpl) << std::endl;
return 0;
}
imprimirá:
0 = 4 1 = 6.6 2 = hello 3 = 7 terminate called after throwing an instance of ''std::out_of_range'' what(): Tuple element out of range. Aborted
(El boost::variant<T...>
requiere g ++ 4.7)