¿Cómo expando una tupla en argumentos de función de plantilla variadic?


136

Considere el caso de una función con plantilla con argumentos de plantilla variadic:

template<typename Tret, typename... T> Tret func(const T&... t);

Ahora tengo una tupla tde valores. ¿Cómo llamo func()usando los valores de tupla como argumentos? He leído sobre el bind()objeto de función, con call()función, y también la apply()función en diferentes documentos obsoletos. La implementación de GNU GCC 4.4 parece tener una call()función en la bind()clase, pero hay muy poca documentación sobre el tema.

Algunas personas sugieren hacks recursivos escritos a mano, pero el verdadero valor de los argumentos de plantilla variadic es poder usarlos en casos como el anterior.

¿Alguien tiene una solución a is, o insinúa dónde leer sobre esto?


55
El estándar C ++ 14 tiene una solución ver; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen

1
La idea es desempaquetar la tupla en una sola explosión variada, usando integer_sequence, vea en.cppreference.com/w/cpp/utility/integer_sequence
Skeen

66
Al tener un integer_sequence S, simplemente llama a su función como func(std::get<S>(tuple)...)y deja que el compilador se encargue del resto.
Skeen

1
Si usa C ++ 17 o posterior, ignore esta respuesta y vea la siguiente usando std :: apply
lewis

Respuestas:


46

Aquí está mi código si alguien está interesado

Básicamente, en el momento de la compilación, el compilador desenrolla recursivamente todos los argumentos en varias llamadas de función inclusivas <N> -> llamadas <N-1> -> llamadas ... -> llamadas <0>, que es la última y el compilador se optimizará. las diversas llamadas a funciones intermedias solo mantienen la última que es el equivalente de func (arg1, arg2, arg3, ...)

Se proporcionan 2 versiones, una para una función invocada en un objeto y la otra para una función estática.

#include <tr1/tuple>

/**
 * Object Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_obj_func
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_obj_func<N-1>::applyTuple( pObj, f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_obj_func<0>
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    (pObj->*f)( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename T, typename... ArgsF, typename... ArgsT >
void applyTuple( T* pObj,
                 void (T::*f)( ArgsF... ),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_obj_func<sizeof...(ArgsT)>::applyTuple( pObj, f, t );
}

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_func
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_func<N-1>::applyTuple( f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_func<0>
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    f( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename... ArgsF, typename... ArgsT >
void applyTuple( void (*f)(ArgsF...),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_func<sizeof...(ArgsT)>::applyTuple( f, t );
}

// ***************************************
// Usage
// ***************************************

template < typename T, typename... Args >
class Message : public IMessage
{

  typedef void (T::*F)( Args... args );

public:

  Message( const std::string& name,
           T& obj,
           F pFunc,
           Args... args );

private:

  virtual void doDispatch( );

  T*  pObj_;
  F   pFunc_;
  std::tr1::tuple<Args...> args_;
};

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
Message<T, Args...>::Message( const std::string& name,
                              T& obj,
                              F pFunc,
                              Args... args )
: IMessage( name ),
  pObj_( &obj ),
  pFunc_( pFunc ),
  args_( std::forward<Args>(args)... )
{

}

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
void Message<T, Args...>::doDispatch( )
{
  try
  {
    applyTuple( pObj_, pFunc_, args_ );
  }
  catch ( std::exception& e )
  {

  }
}

2
¿Es posible adaptar esto para trabajar en un caso donde la "función" en cuestión es en realidad un constructor?
HighCommander4

¿Podría dar un ejemplo de lo que quiere hacer y podemos ir desde allí?
David

Esta solución solo proporciona una sobrecarga de tiempo de compilación y al final se simplificará a (pObj -> * f) (arg0, arg, 1, ... argN); ¿Derecha?
Goofy

Sí, el compilador comprimirá las llamadas de funciones múltiples en la final como si lo hubiera escrito usted mismo, lo cual es la belleza de todo este material de meta programación.
David

todas las tr1cosas se pueden sacar ahora con c ++ 11
Ryan Haining

37

En C ++ 17 puedes hacer esto:

std::apply(the_function, the_tuple);

Esto ya funciona en Clang ++ 3.9, usando std :: experimental :: apply.

Respondiendo al comentario que dice que esto no funcionará si the_functiontiene una plantilla, lo siguiente es una solución alternativa:

#include <tuple>

template <typename T, typename U> void my_func(T &&t, U &&u) {}

int main(int argc, char *argv[argc]) {

  std::tuple<int, float> my_tuple;

  std::apply([](auto &&... args) { my_func(args...); }, my_tuple);

  return 0;
}

Esta solución alternativa es una solución simplificada al problema general de pasar conjuntos de sobrecarga y plantillas de funciones donde se esperaría una función. La solución general (una que se ocupa del reenvío perfecto, constexpr-ness y noexcept-ness) se presenta aquí: https://blog.tartanllama.xyz/passing-overload-sets/ .


De acuerdo con el código de ejemplo en std :: apply , no parece funcionar si the_functiontiene una plantilla.
Zitrax

1
@Zitrax Puede especificar los argumentos de plantilla de la función:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Erbureth dice Reinstate Monica

Esta es la solución más simple y elegante. Y funciona de maravilla. ¡Muchas gracias, señor Alaggan! +100 votos
Elliott

36

En C ++ hay muchas formas de expandir / desempaquetar tuplas y aplicar esos elementos de tupla a una función de plantilla variable. Aquí hay una pequeña clase auxiliar que crea una matriz de índice. Se usa mucho en la metaprogramación de plantillas:

// ------------- UTILITY---------------
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

Ahora el código que hace el trabajo no es tan grande:

 // ----------UNPACK TUPLE AND APPLY TO FUNCTION ---------
#include <tuple>
#include <iostream> 

using namespace std;

template<class Ret, class... Args, int... Indexes > 
Ret apply_helper( Ret (*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup) 
{ 
    return pf( forward<Args>( get<Indexes>(tup))... ); 
} 

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), const tuple<Args...>&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), tuple<Args...>(tup));
}

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), tuple<Args...>&&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), forward<tuple<Args...>>(tup));
}

La prueba se muestra a continuación:

// --------------------- TEST ------------------
void one(int i, double d)
{
    std::cout << "function one(" << i << ", " << d << ");\n";
}
int two(int i)
{
    std::cout << "function two(" << i << ");\n";
    return i;
}

int main()
{
    std::tuple<int, double> tup(23, 4.5);
    apply(one, tup);

    int d = apply(two, std::make_tuple(2));    

    return 0;
}

No soy un gran experto en otros idiomas, pero supongo que si estos idiomas no tienen esa funcionalidad en su menú, no hay forma de hacerlo. Al menos con C ++ puedes, y creo que no es tan complicado ...


"... y aplica esos elementos de tupla a una función de plantilla variable" . Sin embargo, la sección de prueba solo contiene funciones variadas que no son plantillas. Si agrego uno como template<class ... T> void three(T...) {}e intento usar apply, no se compila.
Zitrax

32

Creo que esta es la solución más elegante (y se reenvía de manera óptima):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a)
        -> decltype(Apply<N-1>::apply(
            ::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        ))
    {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a)
        -> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
    {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t)
    -> decltype(Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
{
    return Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

Ejemplo de uso:

void foo(int i, bool b);

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&foo, t);
}

Desafortunadamente, GCC (4.6 al menos) no puede compilar esto con "lo siento, sin implementar: sobrecarga de manipulación" (lo que simplemente significa que el compilador aún no implementa completamente la especificación C ++ 11), y dado que usa plantillas variadas, no funciona en MSVC, por lo que es más o menos inútil. Sin embargo, una vez que haya un compilador que admita la especificación, será el mejor enfoque en mi humilde opinión. (Nota: no es tan difícil modificar esto para poder solucionar las deficiencias en GCC, o implementarlo con el preprocesador Boost, pero arruina la elegancia, así que esta es la versión que estoy publicando).

GCC 4.7 ahora admite este código perfectamente.

Editar: se agregó hacia adelante alrededor de la llamada a la función real para admitir el formulario de referencia rvalue * esto en caso de que esté usando clang (o si alguien más realmente llega a agregarlo).

Editar: se agregó la falta hacia adelante alrededor del objeto de función en el cuerpo de la función de aplicación no miembro. Gracias a pheedbaq por señalar que faltaba.

Editar: Y aquí está la versión C ++ 14 solo porque es mucho mejor (en realidad aún no se compila):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a) {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a) {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t) {
    return Apply< ::std::tuple_size< ::std::decay_t<T>
      >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

Aquí hay una versión para funciones miembro (¡no se ha probado mucho!):

using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.

template<size_t N>
struct ApplyMember
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&& t, A&&... a) ->
        decltype(ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...))
    {
        return ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...);
    }
};

template<>
struct ApplyMember<0>
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
        decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
    {
        return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
    }
};

// C is the class, F is the member function, T is the tuple.
template<typename C, typename F, typename T>
inline auto apply(C&& c, F&& f, T&& t) ->
    decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
{
    return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
}
// Example:

class MyClass
{
public:
    void foo(int i, bool b);
};

MyClass mc;

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&mc, &MyClass::foo, t);
}

1
+1 de las respuestas enumeradas, la suya fue lo más cerca que pude trabajar con argumentos cuyos argumentos son vectores ... ... pero todavía recibo errores de compilación. ideone.com/xH5kBH Si compila esto con -DDIRECT_CALL y lo ejecuta, verá cuál debería ser el resultado. De lo contrario, aparece un error de compilación (creo que decltype no es lo suficientemente inteligente como para resolver mi caso especial), con gcc 4.7.2.
kfmfe04

3
La versión de gcc en ideaone es demasiado antigua para que esto pase, no es compatible con la sobrecarga del tipo de retorno decltype destrozado. He probado este código relativamente a fondo en gcc 4.7.2, y no he tenido ningún problema. Con gcc 4.8, puede usar la nueva función de valor de retorno automático de C ++ 17 para evitar todos los tipos de retorno finales desagradables.
DRayX

1
Por curiosidad, en la función no miembro apply, ¿por qué fno se envuelve con una std::forwardllamada, ya que está en el tipo de retorno? ¿No es necesario?
Brett Rossier

3
Por curiosidad, intenté compilar esto en GCC 4.8, y foo('x', true)compilé exactamente el mismo código de ensamblaje que apply(foo, ::std::make_tuple('x', true))con cualquier nivel de optimización además de -O0.
DRayX

2
Con C ++ 14 integer_sequenceincluso obtienes una implementación casi correcta apply()en su ejemplo. mira mi respuesta a continuación.
PeterSom

28
template<typename F, typename Tuple, std::size_t ... I>
auto apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename Tuple>
auto apply(F&& f, Tuple&& t) {
    using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}

Esto está adaptado del borrador de C ++ 14 usando index_sequence. Podría proponer que se aplique en un futuro estándar (TS).


1

La noticia no se ve bien.

Después de leer el borrador del estándar recién publicado , no veo una solución integrada para esto, lo que parece extraño.

El mejor lugar para preguntar sobre estas cosas (si aún no lo ha hecho) es comp.lang.c ++. Moderado, porque algunas personas involucradas en la redacción de la publicación estándar allí regularmente.

Si revisa este hilo , alguien tiene la misma pregunta (¡tal vez sea usted, en cuyo caso encontrará que esta respuesta completa es un poco frustrante!), Y se sugieren algunas implementaciones desagradables.

Me preguntaba si sería más simple hacer que la función acepte a tuple, ya que la conversión de esa manera es más fácil. Pero esto implica que todas las funciones deben aceptar tuplas como argumentos, para una máxima flexibilidad, y de modo que solo demuestre la extrañeza de no proporcionar una expansión incorporada de tupla para el paquete de argumentos de función.

Actualización: el enlace de arriba no funciona - intente pegar esto:

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/750fa3815cdaac45/d8dc09e34bbb9661?lnk=gst&q=tuple+variadic#d8dc09e34bbb9661


Me pregunto por qué incluso se molestan en tener nociones separadas de tupla y paquete de argumentos de función. Tal vez en un compilador conforme sean intercambiables, pero no he visto una indicación de eso en ningún lado que haya leído sobre ellos.
Daniel Earwicker el

2
Debido a que tuple <int, char, string> es necesario como un tipo separado; como es la capacidad de hacer una función que no requiere make_type en el medio de cada llamada.
coppro

1
Además, el mejor lugar no es comp.lang.c ++. Moderado. Las preguntas sobre C ++ 1x casi siempre se dirigen mejor a comp.std.c ++.
coppro

1

Todas estas implementaciones son buenas. Pero debido al uso del puntero al compilador de funciones miembro, a menudo no se puede alinear la llamada a la función de destino (al menos gcc 4.8 no se puede, no importa qué. ¿Por qué gcc no se pueden determinar los punteros de función en línea que se pueden determinar? )

Pero las cosas cambian si se envía el puntero a la función miembro como argumentos de plantilla, no como parámetros de función:

/// from https://stackoverflow.com/a/9288547/1559666
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; };

template<typename TT>
using makeSeq = typename gens< std::tuple_size< typename std::decay<TT>::type >::value >::type;


// deduce function return type
template<class ...Args>
struct fn_type;

template<class ...Args>
struct fn_type< std::tuple<Args...> >{

    // will not be called
    template<class Self, class Fn>
    static auto type_helper(Self &self, Fn f) -> decltype((self.*f)(declval<Args>()...)){
        //return (self.*f)(Args()...);
        return NULL;
    }
};

template<class Self, class ...Args>
struct APPLY_TUPLE{};

template<class Self, class ...Args>
struct APPLY_TUPLE<Self, std::tuple<Args...>>{
    Self &self;
    APPLY_TUPLE(Self &self): self(self){}

    template<class T, T (Self::* f)(Args...),  class Tuple>
    void delayed_call(Tuple &&list){
        caller<T, f, Tuple >(forward<Tuple>(list), makeSeq<Tuple>() );
    }

    template<class T, T (Self::* f)(Args...), class Tuple, int ...S>
    void caller(Tuple &&list, const seq<S...>){
        (self.*f)( std::get<S>(forward<Tuple>(list))... );
    }
};

#define type_of(val) typename decay<decltype(val)>::type

#define apply_tuple(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            > \
            (tuple);

Y uso:

struct DelayedCall
{  
    void call_me(int a, int b, int c){
        std::cout << a+b+c;
    }

    void fire(){
        tuple<int,int,int> list = make_tuple(1,2,3);
        apply_tuple(*this, call_me, list); // even simpler than previous implementations
    }
};

Prueba de inlinable http://goo.gl/5UqVnC


Con pequeños cambios, podemos "sobrecargar" apply_tuple:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(X,##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define VARARG_IMPL_(base, count, ...) base##count(__VA_ARGS__)
#define VARARG_IMPL(base, count, ...) VARARG_IMPL_(base, count, __VA_ARGS__)
#define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)

#define apply_tuple2(fname, tuple) apply_tuple3(*this, fname, tuple)
#define apply_tuple3(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            /* ,decltype(tuple) */> \
            (tuple);
#define apply_tuple(...) VARARG(apply_tuple, __VA_ARGS__)

...

apply_tuple(obj, call_me, list);
apply_tuple(call_me, list);       // call this->call_me(list....)

Además, esta es la única solución que funciona con funciones de plantilla.


1

1) si tiene una estructura ready_meter parameter_pack como argumento de función, puede usar std :: tie así:

template <class... Args>
void tie_func(std::tuple<Args...> t, Args&... args)
{
 std::tie<Args...>(args...) = t;
}

int main()
{
 std::tuple<int, double, std::string> t(2, 3.3, "abc");

 int i;
 double d;
 std::string s;

 tie_func(t, i, d, s);

 std::cout << i << " " << d << " " << s << std::endl;
}

2) si no tienes un argumento de parampack listo, tendrás que desenrollar la tupla así

#include <tuple>
#include <functional>
#include <iostream>



template<int N>
struct apply_wrap {
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>& t, UnpackedArgs... args )
    {
        return apply_wrap<N-1>::applyTuple( f, t, std::get<N-1>( t ), args... );
    }
};


template<>
struct apply_wrap<0>
{
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>&, UnpackedArgs... args )
    {
        return f( args... );
    }
};



template<typename R, typename... TupleArgs>
R applyTuple( std::function<R(TupleArgs...)>& f, std::tuple<TupleArgs...> const& t )
{
    return apply_wrap<sizeof...(TupleArgs)>::applyTuple( f, t );
}



int fac(int n)
{
    int r=1;
    for(int i=2; i<=n; ++i)
        r *= i;
    return r;
}



int main()
{
    auto t = std::make_tuple(5);
    auto f = std::function<decltype(fac)>(&fac);
    cout << applyTuple(f, t);
}

0

Qué tal esto:

// Warning: NOT tested!
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

using std::declval;
using std::forward;
using std::get;
using std::integral_constant;
using std::size_t;
using std::tuple;

namespace detail
{
    template < typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, 0u>, tuple<T...> const &t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    { return forward<Func>( f )( forward<Args>(a)... ); }

    template < size_t Index, typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, Index>, tuple<T...> const&t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    {
        return explode_tuple( integral_constant<size_t, Index - 1u>{}, t,
         forward<Func>(f), get<Index - 1u>(t), forward<Args>(a)... );
    }
}

template < typename Func, typename ...T >
auto  run_tuple( Func &&f, tuple<T...> const &t )
 -> decltype( forward<Func>(f)(declval<T const>()...) )
{
    return detail::explode_tuple( integral_constant<size_t, sizeof...(T)>{}, t,
     forward<Func>(f) );
}

template < typename Tret, typename ...T >
Tret  func_T( tuple<T...> const &t )
{ return run_tuple( &func<Tret, T...>, t ); }

La run_tupleplantilla de función toma la tupla dada y pasa sus elementos individualmente a la función dada. Lleva a cabo su trabajo llamando recursivamente a sus plantillas de función auxiliar explode_tuple. Es importante que run_tuplepase el tamaño de la tupla a explode_tuple; ese número actúa como un contador de cuántos elementos extraer.

Si la tupla está vacía, run_tuplellama a la primera versión de explode_tuplecon la función remota como el único otro argumento. La función remota se llama sin argumentos y hemos terminado. Si la tupla no está vacía, se pasa un número mayor a la segunda versión de explode_tuple, junto con la función remota. Una llamada recursiva aexplode_tuplese realiza, con los mismos argumentos, excepto que el número de contador se reduce en uno y (una referencia a) el último elemento de tupla se agrega como argumento después de la función remota. En una llamada recursiva, el contador no es cero, y se realiza otra llamada con el contador disminuido nuevamente y el siguiente elemento sin referencia se inserta en la lista de argumentos después de la función remota pero antes de los otros argumentos insertados, o el contador alcanza cero y la función remota se llama con todos los argumentos acumulados después de ella.

No estoy seguro de tener la sintaxis de forzar una versión particular de una plantilla de función correcta. Creo que puede usar un puntero para funcionar como un objeto de función; el compilador lo arreglará automáticamente.


0

Estoy evaluando MSVS 2013RC, y no pudo compilar algunas de las soluciones anteriores propuestas aquí en algunos casos. Por ejemplo, MSVS no podrá compilar devoluciones "automáticas" si hay demasiados parámetros de función, debido a un límite de imbricación de espacio de nombres (envié esa información a Microsoft para que se corrigiera). En otros casos, necesitamos acceso al retorno de la función, aunque eso también se puede hacer con una lamda: los siguientes dos ejemplos dan el mismo resultado.

apply_tuple([&ret1](double a){ret1 = cos(a); }, std::make_tuple<double>(.2));
ret2 = apply_tuple((double(*)(double))cos, std::make_tuple<double>(.2));

Y gracias nuevamente a aquellos que publicaron respuestas aquí antes que yo, no habría llegado a esto sin él ... así que aquí está:

template<size_t N>
struct apply_impl {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&& t, A&&... a)
    -> decltype(apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
    -> decltype(apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
    }
};

// This is a work-around for MSVS 2013RC that is required in some cases
#if _MSC_VER <= 1800 /* update this when bug is corrected */
template<>
struct apply_impl<6> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&& t, A&&... a)
    -> decltype(std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
};
#endif

template<>
struct apply_impl<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&&, A&&... a)
    -> decltype(std::forward<F>(f)(std::forward<A>(a)...)) {
         return std::forward<F>(f)(std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&&, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::forward<A>(a)...);
    }
};

// Apply tuple parameters on a non-member or static-member function by perfect forwarding
template<typename F, typename T>
inline auto apply_tuple(F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t));
}

// Apply tuple parameters on a member function
template<typename C, typename F, typename T>
inline auto apply_tuple(C*const o, F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t));
}

¿Por qué haces argumento de objeto un puntero constante? ¿No referencia, no referencia constante, no solo puntero? ¿Qué pasa si la función invocable no lo hará const?
tower120

0

Extendiendo la solución de @ David, puede escribir una plantilla recursiva que

  1. No utiliza el (excesivamente detallado, imo) integer_sequence semántica
  2. No utiliza un parámetro de plantilla temporal adicional int N para contar iteraciones recursivas
  3. (Opcional para functors estáticos / globales) utiliza el functor como parámetro de plantilla para la optimización en tiempo de compilación

P.ej:

template <class F, F func>
struct static_functor {
    template <class... T, class... Args_tmp>
    static inline auto apply(const std::tuple<T...>& t, Args_tmp... args)
            -> decltype(func(std::declval<T>()...)) {
        return static_functor<F,func>::apply(t, args...,
                std::get<sizeof...(Args_tmp)>(t));
    }
    template <class... T>
    static inline auto apply(const std::tuple<T...>& t, T... args)
            -> decltype(func(args...)) {
        return func(args...);
    }
};

static_functor<decltype(&myFunc), &myFunc>::apply(my_tuple);

Alternativamente, si su functor no está definido en tiempo de compilación (por ejemplo, una constexprinstancia que no sea functor o una expresión lambda), puede usarlo como un parámetro de función en lugar de un parámetro de plantilla de clase y, de hecho, eliminar completamente la clase que lo contiene:

template <class F, class... T, class... Args_tmp>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        Args_tmp... args) -> decltype(func(std::declval<T>()...)) {
    return apply_functor(func, t, args..., std::get<sizeof...(Args_tmp)>(t));
}
template <class F, class... T>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        T... args) -> decltype(func(args...)) {
    return func(args...);
}

apply_functor(&myFunc, my_tuple);

Para los invocables de la función de puntero a miembro, puede ajustar cualquiera de las piezas de código anteriores de manera similar a la respuesta de @ David.

Explicación

En referencia al segundo fragmento de código, hay dos funciones de plantilla: la primera toma el functor func, la tupla tcon tipos T...y un paquete de parámetrosargs de tipos deArgs_tmp... . Cuando se le llama, agrega recursivamente los objetos del tpaquete de parámetros uno a la vez, desde el principio (0 ) hasta el final, y vuelve a llamar a la función con el nuevo paquete de parámetros incrementado.

La firma de la segunda función es casi idéntica a la primera, excepto que usa el tipo T...para el paquete de parámetros args. Por lo tanto, una vez argsen la primera función se llena por completo con los valores det , su tipo será T...(en psuedo-code, typeid(T...) == typeid(Args_tmp...)) y, por lo tanto, el compilador llamará a la segunda función sobrecargada, que a su vez llama func(args...).

El código en el ejemplo del functor estático funciona de manera idéntica, y el functor se usa como argumento de plantilla de clase.


Cualquier comentario sobre la optimización en tiempo de compilación de la primera opción sería apreciado, por lo que puedo hacer que mi respuesta sea más completa (y tal vez aprender algo nuevo).
CrepeGoat

-3

¿Por qué no simplemente envolver sus argumentos variadic en una clase de tupla y luego usar la recursión en tiempo de compilación (vea el enlace ) para recuperar el índice que le interesa? Me parece que desempaquetar plantillas variadic en un contenedor o colección puede no ser de tipo seguro con tipos heterogéneos

template<typename... Args>
auto get_args_as_tuple(Args... args) -> std::tuple<Args...> 
{
    return std::make_tuple(args);
}

66
La pregunta era al revés. No Args...-> tuple, pero tuple-> Args....
Xeo

-4

Esta solución simple me funciona:

template<typename... T>
void unwrap_tuple(std::tuple<T...>* tp)
{
    std::cout << "And here I have the tuple types, all " << sizeof...(T) << " of them" << std::endl;
}

int main()
{
    using TupleType = std::tuple<int, float, std::string, void*>;

    unwrap_tuple((TupleType*)nullptr); // trick compiler into using template param deduction
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.