
flexografía
Estoy tratando de almacenar en un std::tuple
un número variable de valores, que luego se usarán como argumentos para una llamada a un puntero de función que coincida con los tipos almacenados.
Creé 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 con std::tuple
o 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 forma de hacerlo para enviar una llamada de función.
La motivación real para esto es algo más compleja y, de todos modos, 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 el deseo de descomprimirlo en una llamada de función es mío. Esto descarta el uso std::bind
como una forma barata de eludir el problema subyacente.
¿Cuál es una forma 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?
Necesita crear un paquete de parámetros de números y descomprimirlos
template<int ...>
struct seq { };
template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };
template<int ...S>
struct gens<0, S...> {
typedef seq<S...> type;
};
// ...
void delayed_dispatch() {
callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
void callFunc(seq<S...>) {
func(std::get<S>(params) ...);
}
// ...

davidalto
La solución C++17 es simplemente para usar std::apply
:
auto f = [](int a, double b, std::string c) { std::cout<<a<<" "<<b<<" "<<c<< std::endl; };
auto params = std::make_tuple(1,2.0,"Hello");
std::apply(f, params);
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 aún falta en este hilo. EDITAR: No, en realidad está allí en la respuesta de Walter.
Esta función está dada:
void f(int a, double b, void* c)
{
std::cout << a << ":" << b << ":" << c << std::endl;
}
Llámalo con el siguiente fragmento:
template<typename Function, typename Tuple, size_t ... I>
auto call(Function f, Tuple t, std::index_sequence<I ...>)
{
return f(std::get<I>
}
template<typename Function, typename Tuple>
auto call(Function f, Tuple t)
{
static constexpr auto size = std::tuple_size<Tuple>::value;
return call(f, t, std::make_index_sequence<size>{});
}
Ejemplo:
int main()
{
std::tuple<int, double, int*> t;
//or std::array<int, 3> t;
//or std::pair<int, double> t;
call(f, t);
}
MANIFESTACIÓN

Faheem Mitha
Esta es una versión compilable completa 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.
###################
johannes.cc
###################
#include <tuple>
#include <iostream>
using std::cout;
using std::endl;
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
double foo(int x, float y, double z)
{
return x + y + z;
}
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
double (*func)(Args...);
double delayed_dispatch()
{
return callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
double callFunc(seq<S...>)
{
return func(std::get<S>(params) ...);
}
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
int main(void)
{
gens<10> g;
gens<10>::type s;
std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
save_it_for_later<int,float, double> saved = {t, foo};
cout << saved.delayed_dispatch() << endl;
}
#pragma GCC diagnostic pop
Uno puede usar el siguiente archivo SConstruct
#####################
SConstruct
#####################
#!/usr/bin/python
env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11")
env.Program(target="johannes", source=["johannes.cc"])
En mi máquina, esto da
g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc
g++-4.7 -o johannes johannes.o

walter
Aquí hay una solución C++ 14.
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
void (*func)(Args...);
template<std::size_t ...I>
void call_func(std::index_sequence<I...>)
{ func(std::get<I>(params)...); }
void delayed_dispatch()
{ call_func(std::index_sequence_for<Args...>{}); }
};
Esto todavía necesita una función auxiliar (call_func
). Dado que este es un idioma común, tal vez el estándar debería admitirlo directamente como std::call
con posible implementación
// helper class
template<typename R, template<typename...> class Params, typename... Args, std::size_t... I>
R call_helper(std::function<R(Args...)> const&func, Params<Args...> const¶ms, std::index_sequence<I...>)
{ return func(std::get<I>(params)...); }
// "return func(params...)"
template<typename R, template<typename...> class Params, typename... Args>
R call(std::function<R(Args...)> const&func, Params<Args...> const¶ms)
{ return call_helper(func,params,std::index_sequence_for<Args...>{}); }
Entonces nuestro envío retrasado se convierte en
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
std::function<void(Args...)> func;
void delayed_dispatch()
{ std::call(func,params); }
};

Karel Petranek
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 (los invocar función). Como beneficio adicional, Boost Fusion también funciona con compiladores C++03.
solución c ++ 14. Primero, algunos repetitivos de utilidad:
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={}){
return index_over( std::make_index_sequence<N>{} );
}
Estos le permiten llamar a una lambda con una serie de enteros en tiempo de compilación.
void delayed_dispatch() {
auto indexer = index_upto<sizeof...(Args)>();
indexer([&](auto...Is){
func(std::get<Is>(params)...);
});
}
y hemos terminado.
index_upto
y index_over
le permite trabajar con paquetes de parámetros sin tener que generar una nueva sobrecarga externa.
Por supuesto, en C++17 solo
void delayed_dispatch() {
std::apply( func, params );
}
Ahora, si nos gusta eso, en c++14 podemos escribir:
namespace notstd {
template<class T>
constexpr auto tuple_size_v = std::tuple_size<T>::value;
template<class F, class Tuple>
decltype(auto) apply( F&& f, Tuple&& tup ) {
auto indexer = index_upto<
tuple_size_v<std::remove_reference_t<Tuple>>
>();
return indexer(
[&](auto...Is)->decltype(auto) {
return std::forward<F>(f)(
std::get<Is>(std::forward<Tuple>(tup))...
);
}
);
}
}
con relativa facilidad y obtenga la sintaxis más limpia de C++17 lista para su envío.
void delayed_dispatch() {
notstd::apply( func, params );
}
solo reemplaza notstd
con std
cuando tu compilador se actualiza y bob es tu tío.
Pensando un poco más en el problema según la respuesta dada, encontré otra forma de resolver el mismo problema:
template <int N, int M, typename D>
struct call_or_recurse;
template <typename ...Types>
struct dispatcher {
template <typename F, typename ...Args>
static void impl(F f, const std::tuple<Types...>& params, Args... args) {
call_or_recurse<sizeof...(Args), sizeof...(Types), dispatcher<Types...> >::call(f, params, args...);
}
};
template <int N, int M, typename D>
struct call_or_recurse {
// recurse again
template <typename F, typename T, typename ...Args>
static void call(F f, const T& t, Args... args) {
D::template impl(f, t, std::get<M-(N+1)>
}
};
template <int N, typename D>
struct call_or_recurse<N,N,D> {
// do the call
template <typename F, typename T, typename ...Args>
static void call(F f, const T&, Args... args) {
f(args...);
}
};
Lo que requiere cambiar la implementación de delayed_dispatch()
para:
void delayed_dispatch() {
dispatcher<Args...>::impl(func, params);
}
Esto funciona convirtiendo recursivamente el std::tuple
en un paquete de parámetros por derecho propio. call_or_recurse
se necesita como una especialización para terminar la recursividad con la llamada real, que simplemente desempaqueta el paquete de parámetros completo.
No estoy seguro de que esta sea de todos modos una solución “mejor”, pero es otra forma de pensar y resolverlo.
Como otra solución alternativa, puede utilizar enable_if
para formar algo posiblemente más simple que mi solución anterior:
#include <iostream>
#include <functional>
#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...);
template <typename ...Actual>
typename std::enable_if<sizeof...(Actual) != sizeof...(Args)>::type
delayed_dispatch(Actual&& ...a) {
delayed_dispatch(std::forward<Actual>(a)..., std::get<sizeof...(Actual)>(params));
}
void delayed_dispatch(Args ...args) {
func(args...);
}
};
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();
}
La primera sobrecarga solo toma un argumento más de la tupla y lo 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 que la segunda sería viable.
¿Por qué no puedes simplemente usar
auto saved = std::bind(f, a, b, c);
… luego solo llamasaved()
?– Carlos Salvia
11 de marzo de 2015 a las 17:11
No siempre mi interfaz para controlar. Recibo una tupla por contrato de otra persona y quiero hacer cosas con ella posteriormente.
– Flexografía
♦
17 de enero de 2017 a las 17:58