Estoy tratando de almacenar un std::tuple
número variable de valores, que luego se utilizarán como argumentos para una llamada a un puntero de función que coincida con los tipos almacenados.
He creado un ejemplo simplificado que muestra el problema que estoy luchando por resolver:
#include <iostream>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
void delayed_dispatch() {
// How can I "unpack" params to call func?
func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
// But I *really* don't want to write 20 versions of dispatch so I'd rather
// write something like:
func(params...); // Not legal
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
Normalmente, para problemas relacionados std::tuple
con plantillas variadas, escribiría otra plantilla como template <typename Head, typename ...Tail>
para evaluar recursivamente todos los tipos uno por uno, pero no puedo ver una manera de hacerlo para enviar una llamada a la función.
La verdadera motivación para esto es algo más compleja y, en general, es solo un ejercicio de aprendizaje. Puede suponer que me entregó la tupla por contrato desde otra interfaz, por lo que no se puede cambiar, pero que el deseo de descomprimirlo en una llamada de función es mío. Esto descarta usarlo std::bind
como una forma barata de esquivar el problema subyacente.
¿Cuál es una manera limpia de despachar la llamada usando el std::tuple
, o una mejor forma alternativa de lograr el mismo resultado neto de almacenar / reenviar algunos valores y un puntero de función hasta un punto futuro arbitrario?
auto saved = std::bind(f, a, b, c);
... luego llamarsaved()
?Respuestas:
Necesita construir un paquete de números de parámetros y descomprimirlos
fuente
struct gens
definición genérica (la que hereda de una derivación expandida de lo mismo). Veo que finalmente llega a la especialización con 0. Si el estado de ánimo le conviene y tiene los ciclos de repuesto, si puede ampliar eso y cómo se utiliza para esto, estaría eternamente agradecido. Y desearía poder votar esto cientos de veces. Me he divertido más jugando con las tangentes de este código. Gracias.seq<0, 1, .., N-1>
. Cómo funciona:gens<5>: gens<4, 4>: gens<3, 3, 4>: gens<2, 2, 3, 4> : gens<1, 1, 2, 3, 4> : gens<0, 0, 1, 2, 3, 4>
. El último tipo es especializado, crearseq<0, 1, 2, 3, 4>
. Truco bastante inteligente.gens
:template <int N, int... S> struct gens { typedef typename gens<N-1, N-1, S...>::type type; };
std::integer_sequence<T, N>
y su especialización parastd::size_t
,std::index_sequence<N>
más sus funciones auxiliares asociadasstd::make_in(teger|dex)_sequence<>()
ystd::index_sequence_for<Ts...>()
. Y en C ++ 17 hay muchas otras cosas buenas integradas en la biblioteca, particularmente incluyendostd::apply
ystd::make_from_tuple
, que manejaría el desempaquetado y los bits de llamadaLa solución C ++ 17 es simplemente usar
std::apply
:Simplemente sentí que debería indicarse una vez en una respuesta en este hilo (después de que ya apareció en uno de los comentarios).
La solución básica de C ++ 14 todavía falta en este hilo. EDITAR: No, en realidad está ahí en la respuesta de Walter.
Esta función está dada:
Llámalo con el siguiente fragmento:
Ejemplo:
MANIFESTACIÓN
fuente
http://coliru.stacked-crooked.com/a/8ea8bcc878efc3cb
std::make_unique
directamente? ¿Necesita instancia de función concreta? 2. ¿Por quéstd::move(ts)...
si podemos cambiar[](auto... ts)
a[](auto&&... ts)
?std::make_unique
espera una tupla, y una tupla se puede crear a partir de una tupla desempaquetada solo a través de otra llamada astd::make_tuple
. Esto es lo que he hecho en el lambda (aunque es muy redundante, ya que también puede simplemente copiar la tupla en el puntero único sin ningún usocall
).Esta es una versión completa compilable de la solución de Johannes a la pregunta de awoodland, con la esperanza de que pueda ser útil para alguien. Esto se probó con una instantánea de g ++ 4.7 en Debian squeeze.
Uno puede usar el siguiente archivo SConstruct
En mi máquina, esto da
fuente
Aquí hay una solución C ++ 14.
Esto todavía necesita una función auxiliar (
call_func
). Dado que este es un idioma común, quizás el estándar debería admitirlo directamente comostd::call
con una posible implementaciónEntonces nuestro despacho retrasado se convierte en
fuente
std::call
. El zoológico caótico de C ++ 14integer_sequence
y losindex_sequence
tipos de ayuda se explican aquí: en.cppreference.com/w/cpp/utility/integer_sequence Observe la notable ausencia destd::make_index_sequence(Args...)
, por lo que Walter se vio obligado a entrar en la sintaxis más complicadastd::index_sequence_for<Args...>{}
.Esto es un poco complicado de lograr (aunque es posible). Le aconsejo que use una biblioteca donde esto ya está implementado, a saber, Boost.Fusion (la función de invocación ). Como beneficio adicional, Boost Fusion también funciona con compiladores C ++ 03.
fuente
c ++ 14solución. Primero, algunos ejemplos de utilidad:
Estos le permiten llamar a una lambda con una serie de enteros en tiempo de compilación.
y hemos terminado
index_upto
y leindex_over
permite trabajar con paquetes de parámetros sin tener que generar nuevas sobrecargas externas.Por supuesto, en c ++ 17 tu solo
Ahora, si nos gusta eso, en c ++ 14 podemos escribir:
relativamente fácil y obtener el limpiador c ++ 17 sintaxis lista para enviar.
simplemente reemplace
notstd
constd
cuando su compilador se actualice y bob sea su tío.fuente
std::apply
<- música para mis oídosindex_upto
y menos flexible. ;) Intente llamarfunc
con los argumentos al revés conindex_upto
ystd::apply
respectivamente. Es cierto que quién diablos quiere invocar una función de una tupla al revés.std::tuple_size_v
es C ++ 17, por lo que para la solución C ++ 14 que tendría que ser reemplazado portypename std::tuple_size<foo>::value
value
que no sea un tipo. Pero arreglado de todos modos.sizeof...(Types)
. Me gusta tu solución sin eltypename
.Pensando en el problema un poco más en función de la respuesta dada, he encontrado otra forma de resolver el mismo problema:
Lo que requiere cambiar la implementación de
delayed_dispatch()
a:Esto funciona mediante la conversión recursiva
std::tuple
en un paquete de parámetros por derecho propio.call_or_recurse
es necesario como especialización para terminar la recursión con la llamada real, que simplemente desempaqueta el paquete de parámetros completado.No estoy seguro de que sea una solución "mejor", pero es otra forma de pensar y resolverlo.
Como otra solución alternativa que puede usar
enable_if
, para formar algo posiblemente más simple que mi solución anterior:La primera sobrecarga solo toma un argumento más de la tupla y la coloca en un paquete de parámetros. La segunda sobrecarga toma un paquete de parámetros coincidentes y luego realiza la llamada real, con la primera sobrecarga deshabilitada en el único caso en el que la segunda sería viable.
fuente
Mi variación de la solución de Johannes usando C ++ 14 std :: index_sequence (y el tipo de retorno de función como parámetro de plantilla RetT):
fuente