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.