Como expandir uma tupla nos argumentos da função de modelo variável?


135

Considere o caso de uma função de modelo com argumentos de modelo variados:

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

Agora, tenho uma tupla tde valores. Como ligo func()usando os valores da tupla como argumentos? Eu li sobre o bind()objeto de função, com call()função, e também a apply()função em diferentes documentos agora obsoletos. A implementação do GNU GCC 4.4 parece ter uma call()função na bind()classe, mas há muito pouca documentação sobre o assunto.

Algumas pessoas sugerem hacks recursivos escritos à mão, mas o verdadeiro valor dos argumentos dos modelos variados é poder usá-los nos casos como acima.

Alguém tem uma solução para é, ou dica sobre onde ler sobre isso?


5
O padrão C ++ 14 possui uma solução see; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
— Skeen

1
A ideia é descompactar o tupla em uma única explosão variádica, usando integer_sequence, consulte en.cppreference.com/w/cpp/utility/integer_sequence
— Skeen

6
Tendo um integer_sequence S, você simplesmente chama sua função como func(std::get<S>(tuple)...)e deixa o compilador lidar com o resto.
— Skeen 15/10

1
Se estiver usando C ++ 17 ou posterior, ignore esta resposta e veja a abaixo usando std :: apply
— lewis

Respostas:


46

Aqui está o meu código, se alguém estiver interessado

Basicamente, no momento da compilação, o compilador desenrola recursivamente todos os argumentos em várias chamadas de função inclusivas <N> -> chamadas <N-1> -> chamadas ... -> chamadas <0>, que é a última e o compilador será otimizado as várias funções intermediárias chamam para manter apenas a última que é equivalente a func (arg1, arg2, arg3, ...)

São fornecidas duas versões, uma para uma função chamada em um objeto e a outra para uma função 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
É possível adaptar isso para funcionar em um caso em que a "função" em questão é realmente um construtor?
— HighCommander4

Você poderia fornecer um exemplo do que você quer fazer e podemos ir a partir daí.
— David

Essa solução fornece apenas uma sobrecarga no tempo de compilação e, no final, será simplificada para (pObj -> * f) (arg0, arg, 1, ... argN); certo?
— Pateta

Sim, o compilador compactará as chamadas de várias funções na final, como se você tivesse escrito você mesmo, o que é a beleza de todo esse material de meta programação.
— David

todo o tr1material pode se retirado agora com c ++ 11
— Ryan Haining

37

No C ++ 17, você pode fazer isso:

std::apply(the_function, the_tuple);

Isso já funciona no Clang ++ 3.9, usando std :: experimental :: apply.

Respondendo ao comentário dizendo que isso não funcionará se the_functionestiver modelado, o seguinte é uma solução 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 solução alternativa é uma solução simplificada para o problema geral de passar conjuntos de sobrecarga e modelo de função onde uma função seria esperada. A solução geral (que cuida do encaminhamento perfeito, do consenso e do não-aceitável) é apresentada aqui: https://blog.tartanllama.xyz/passing-overload-sets/ .


De acordo com o código de exemplo em std :: apply, ele não parece funcionar se the_functionestiver com modelo.
— Zitrax 4/17/17

1
@Zitrax Você pode especificar os argumentos do modelo da função:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
— Erbureth diz Reinstate Monica

Esta é a solução mais simples e elegante. E faz maravilhas. Muito obrigado, M. Alaggan !!!!!! +100 votos
— Elliott,

36

No C ++, existem muitas maneiras de expandir / descompactar a tupla e aplicar esses elementos a uma função de modelo variável. Aqui está uma pequena classe auxiliar que cria uma matriz de índice. É muito usado na metaprogramação de modelos:

// ------------- 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...> 
{}; 

Agora, o código que faz o trabalho não é tão 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));
}

O teste é mostrado abaixo:

// --------------------- 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;
}

Não sou grande especialista em outros idiomas, mas acho que, se esses idiomas não tiverem essa funcionalidade em seu menu, não há como fazê-lo. Pelo menos com C ++ você pode, e acho que não é tão complicado ...


"... e aplique esses elementos de tupla a uma função de modelo variável" . A seção de teste contém apenas funções variadicas que não são do modelo. Se eu adicionar um like template<class ... T> void three(T...) {}e tentar usar apply, ele não compila.
— Zitrax 04/04

32

Acho que essa é a solução mais elegante (e é encaminhada de maneira ideal):

#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));
}

Exemplo de uso:

void foo(int i, bool b);

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

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

Infelizmente, o GCC (4.6 pelo menos) falha ao compilar isso com "desculpe, não foi implementado: sobrecarga de manipulação" (o que significa simplesmente que o compilador ainda não implementa completamente a especificação do C ++ 11) e, como ele usa modelos variados, não trabalhar no MSVC, por isso é mais ou menos inútil. No entanto, quando houver um compilador que suporte as especificações, essa será a melhor abordagem do IMHO. (Nota: não é tão difícil modificar isso para que você possa solucionar as deficiências no GCC ou implementá-lo com o Boost Preprocessor, mas isso arruina a elegância, portanto esta é a versão que estou publicando.)

O GCC 4.7 agora suporta esse código muito bem.

Edit: Adicionado adiante em torno da chamada de função real para oferecer suporte ao formulário de referência rvalue * isso no caso de você estar usando o clang (ou se alguém realmente conseguir adicionar isso).

Editar: adicionado em falta ao redor do objeto da função no corpo da função de não-membro aplicar. Agradecemos a pheedbaq por apontar que estava faltando.

Edit: E aqui está a versão do C ++ 14, já que é muito melhor (na verdade ainda não 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));
}

Aqui está uma versão para funções membro (não muito testada!):

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
Com +1 das respostas listadas, a sua foi a mais próxima que pude trabalhar com argumentos cujos argumentos são vetores ... ... mas ainda estou recebendo erros de compilação. ideone.com/xH5kBH Se você compilar isso com -DDIRECT_CALL e executá-lo, verá qual deve ser a saída. Caso contrário, recebo um erro de compilação (acho que decltype não é inteligente o suficiente para descobrir meu caso especial), com o gcc 4.7.2.
— kfmfe04

3
A versão do gcc no ideaone é antiga, para que isso passe, ele não suporta a sobrecarga do tipo de retorno com tipo de declínio desconfigurado. Testei esse código de maneira relativamente completa no gcc 4.7.2 e não tive problemas. Com o gcc 4.8, você pode usar o novo recurso de valor de retorno automático do C ++ 17 para evitar todos os tipos desagradáveis ​​de retorno à direita.
— DRayX

1
Por curiosidade, na função de não-membro apply, por que fnão é encerrada com uma std::forwardchamada, como é no tipo de retorno? Não é necessário?
— Brett Rossier 6/02/2013

3
Por curiosidade, tentei compilar isso no GCC 4.8 e foo('x', true)compilei exatamente o mesmo código de montagem que apply(foo, ::std::make_tuple('x', true))com qualquer nível de otimização além de -O0.
— DRayX

2
Com o C ++ 14, integer_sequencevocê obtém uma implementação quase correta apply()em seu exemplo. veja minha resposta abaixo.
— 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());
}

Isso é adaptado do rascunho do C ++ 14 usando index_sequence. Eu poderia propor a aplicação em um futuro padrão (TS).


1

As notícias não parecem boas.

Depois de ler o rascunho do padrão recém-lançado , não vejo uma solução integrada para isso, o que parece estranho.

O melhor lugar para perguntar sobre essas coisas (se você ainda não o fez) é o moderador comp.lang.c ++., Porque algumas pessoas envolvidas na redação do post padrão lá regularmente.

Se você verificar esta discussão , alguém terá a mesma pergunta (talvez seja você; nesse caso, você achará toda essa resposta um pouco frustrante!), E algumas implementações desagradáveis ​​serão sugeridas.

Eu apenas me perguntei se seria mais simples fazer a função aceitar a tuple, pois a conversão dessa maneira é mais fácil. Mas isso implica que todas as funções devem aceitar tuplas como argumentos, para máxima flexibilidade e, portanto, apenas demonstra a estranheza de não fornecer uma expansão interna da tupla para o pacote de argumentos da função.

Atualização: o link acima não funciona - tente colar o seguinte:

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


Eu me pergunto por que eles se incomodam em ter noções separadas de tupla e pacote de argumentos de função. Talvez em um compilador em conformidade eles sejam intercambiáveis, mas eu não vi nenhuma indicação disso em nenhum lugar que eu tenha lido sobre eles.
— 21119 Daniel Earwicker

2
Porque tupla <int, char, string> é necessária como um tipo separado; assim como a capacidade de criar uma função que não requer make_type no meio de cada chamada.
— coppro

1
Além disso, o melhor lugar não é moderado comp.lang.c ++. Perguntas sobre C ++ 1x quase sempre são melhor direcionadas para comp.std.c ++.
— coppro

1

Todas essas implementações são boas. Porém, devido ao uso do ponteiro para o compilador de função membro, muitas vezes não é possível alinhar a chamada da função de destino (pelo menos o gcc 4.8 não pode, não importa o quê. Por que o gcc não pode alinhar os ponteiros de função que podem ser determinados? )

Mas as coisas mudam se enviar ponteiro para a função membro como argumentos de modelo, não como parâmetros de função:

/// 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);

E 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
    }
};

Prova de inlinável http://goo.gl/5UqVnC


Com pequenas alterações, podemos "sobrecarregar" 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....)

Além disso, esta é a única solução que funciona com funções de modelo.


1

1) se você tem uma estrutura de parâmetro_pacote pronto como argumento de função, pode apenas usar std :: tie como este:

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) se você não tiver um argumento de parampack pronto, precisará desenrolar a tupla como esta

#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

Que tal agora:

// 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 ); }

O run_tuplemodelo de função pega a tupla especificada e passa seus elementos individualmente para a função especificada. Ele realiza seu trabalho chamando recursivamente seus modelos de função auxiliar explode_tuple. É importante que run_tuplepasse o tamanho da tupla para explode_tuple; esse número atua como um contador para quantos elementos extrair.

Se a tupla estiver vazia, run_tuplechame a primeira versão do explode_tuplecom a função remota como o único outro argumento. A função remota é chamada sem argumentos e terminamos. Se a tupla não estiver vazia, um número maior será passado para a segunda versão do explode_tuple, juntamente com a função remota. Uma chamada recursiva paraexplode_tupleé feita, com os mesmos argumentos, exceto que o número do contador é diminuído em um e (uma referência a) o último elemento da tupla é alinhado como argumento após a função remota. Em uma chamada recursiva, o contador não é zero e outra chamada é feita com o contador diminuído novamente e o próximo elemento não referenciado é inserido na lista de argumentos após a função remota, mas antes dos outros argumentos inseridos, ou o contador atinge zero e a função remota é chamada com todos os argumentos acumulados após ele.

Não tenho certeza se tenho a sintaxe de forçar uma versão específica de um modelo de função corretamente. Eu acho que você pode usar um ponteiro para função como um objeto de função; o compilador irá corrigi-lo automaticamente.


0

Estou avaliando o MSVS 2013RC e ele não conseguiu compilar algumas das soluções anteriores propostas aqui em alguns casos. Por exemplo, o MSVS falhará ao compilar retornos "automáticos" se houver muitos parâmetros de função, devido a um limite de imbricação de namespace (enviei essas informações à Microsoft para corrigi-las). Em outros casos, precisamos acessar o retorno da função, embora isso também possa ser feito com um lamda: os dois exemplos a seguir dão o mesmo 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));

E obrigado novamente àqueles que postaram respostas aqui antes de mim, eu não teria chegado a isso sem ele ... então aqui 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 que você torna o argumento do objeto um ponteiro const? Sem referência, sem referência const, não apenas com ponteiro? E se a função solicitável não for const?
— tower120

0

Estendendo-se à solução de @ David, você pode escrever um modelo recursivo que

  1. Não usa a integer_sequencesemântica (excessivamente verbosa, imo)
  2. Não usa um parâmetro de modelo temporário extra int Npara contar iterações recursivas
  3. (Opcional para functores estáticos / globais) usa o functor como um parâmetro de modelo para otimização em tempo de compilação

Por exemplo:

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);

Como alternativa, se o seu functor não estiver definido em tempo de compilação (por exemplo, uma constexprinstância que não seja do functor ou uma expressão lambda), você poderá usá-lo como parâmetro de função em vez de parâmetro de modelo de classe e, de fato, remover completamente a classe que contém:

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 chamadas de ponteiro para função de membro, você pode ajustar um dos pedaços de código acima da mesma forma que na resposta de @ David.

Explicação

Em referência ao segundo trecho de código, existem duas funções de modelo: a primeira pega o functor func, a tupla tcom tipos T...e um pacote argsde parâmetros de tipos Args_tmp.... Quando chamado, adiciona recursivamente os objetos de tao pacote de parâmetros, um de cada vez, do início ( 0) ao fim e chama a função novamente com o novo pacote de parâmetros incrementado.

A assinatura da segunda função é quase idêntica à primeira, exceto que ela usa o tipo T...para o pacote de parâmetros args. Assim, uma vez que argsa primeira função é completamente preenchida com os valores de t, seu tipo será T...(em código psuedo typeid(T...) == typeid(Args_tmp...)) e, portanto, o compilador chamará a segunda função sobrecarregada, que por sua vez chama func(args...).

O código no exemplo estático do functor funciona de forma idêntica, com o functor usado como argumento do modelo de classe.


quaisquer comentários sobre a otimização em tempo de compilação da primeira opção serão apreciados, para que eu possa tornar minha resposta mais completa (e talvez aprender algo novo).
— CrepeGoat

-3

Por que não apenas agrupar seus argumentos variados em uma classe de tupla e usar a recursão em tempo de compilação (consulte o link ) para recuperar o índice no qual você está interessado. Acho que descompactar modelos variados em um contêiner ou coleção pode não ser do tipo seguro e heterogêneo

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

6
A questão era o contrário. Não Args...-> tuple, mas tuple-> Args....
— Xeo 04/04/12

-4

Esta solução simples funciona para mim:

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
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.