Jak rozwinąć krotkę w argumenty funkcji szablonu wariadycznego?

135

Rozważmy przypadek funkcji opartej na szablonie z różnymi argumentami szablonu:

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

Teraz mam krotkę twartości. Jak wywołać, func()używając wartości krotki jako argumentów? Czytałem o bind()obiekcie funkcji, z call()funkcją, a także o apply()funkcji w różnych, nieaktualnych, dokumentach. Wydaje się, że implementacja GNU GCC 4.4 ma call()funkcję w bind()klasie, ale jest bardzo mało dokumentacji na ten temat.

Niektórzy sugerują ręcznie pisane rekurencyjne hacki, ale prawdziwą wartością argumentów z szablonów wariadycznych jest możliwość ich użycia w przypadkach takich jak powyżej.

Czy ktoś ma rozwiązanie na to, albo podpowiedź, gdzie o tym poczytać?

Xeo
źródło
5
Standard C ++ 14 ma rozwiązanie patrz; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen
1
Chodzi o to, aby rozpakować krotkę w jednym wariadycznym wybuchu, używając integer_sequence, patrz en.cppreference.com/w/cpp/utility/integer_sequence
Skeen
6
Mając an integer_sequence S, po prostu wywołujesz swoją funkcję jako func(std::get<S>(tuple)...)i pozwalasz kompilatorowi zająć się resztą.
Skeen
1
Jeśli używasz C ++ 17 lub nowszego, zignoruj ​​tę odpowiedź i zobacz poniższą, używając std :: apply
lewis

Odpowiedzi:

46

Oto mój kod, jeśli ktoś jest zainteresowany

Zasadniczo w czasie kompilacji kompilator rekurencyjnie rozwinie wszystkie argumenty w różnych wywołaniach funkcji włączających <N> -> wywołania <N-1> -> wywołania ... -> wywołania <0>, który jest ostatnim, a kompilator przeprowadzi optymalizację różne wywołania funkcji pośrednich, aby zachować tylko ostatnią, która jest odpowiednikiem funkcji func (arg1, arg2, arg3, ...)

Podano dwie wersje, jedną dla funkcji wywoływanej na obiekcie, a drugą dla funkcji statycznej.

#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 )
  {

  }
}
David
źródło
2
Czy można to dostosować do pracy w przypadku, gdy dana „funkcja” jest faktycznie konstruktorem?
HighCommander4
Czy mógłbyś podać przykład tego, co chcesz zrobić, i możemy od tego zacząć.
David,
To rozwiązanie zapewnia jedynie narzut czasu kompilacji i na końcu zostanie uproszczone do (pObj -> * f) (arg0, arg, 1, ... argN); dobrze?
Goofy
tak, kompilator skompresuje wiele wywołań funkcji do ostatniej, tak jakbyś ją sam napisał, co jest pięknem całego tego metaprogramowania.
David
wszystkie tr1rzeczy można teraz usunąć za pomocą c ++ 11
Ryan Haining
37

W C ++ 17 możesz to zrobić:

std::apply(the_function, the_tuple);

To już działa w Clang ++ 3.9, używając std :: experimental :: apply.

Odpowiadając na komentarz, który mówi, że to nie zadziała, jeśli the_functionjest szablonem, można obejść ten problem :

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

To obejście jest uproszczonym rozwiązaniem ogólnego problemu z przekazywaniem zestawów przeciążeń i szablonów funkcji, w przypadku których oczekiwana byłaby funkcja. Ogólne rozwiązanie (takie, które zapewnia doskonałe przekazywanie, constexpr-ness i noexcept-ness) jest przedstawione tutaj: https://blog.tartanllama.xyz/passing-overload-sets/ .

Mohammad Alaggan
źródło
Zgodnie z przykładowym kodem na std :: apply , wydaje się, że nie działa, jeśli the_functionjest na szablonie.
Zitrax
1
@Zitrax Możesz określić argumenty szablonu funkcji:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Erbureth mówi Przywróć Monikę
To najprostsze, najbardziej eleganckie rozwiązanie. I działa cuda. Wielkie dzięki, panie Alaggan !!!!!! +100 głosów
Elliott
36

W C ++ istnieje wiele sposobów rozwijania / rozpakowywania krotki i stosowania tych elementów krotki do wariadycznej funkcji szablonu. Oto mała klasa pomocnicza, która tworzy tablicę indeksów. Jest często używany w metaprogramowaniu szablonów:

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

Teraz kod, który wykonuje tę pracę, nie jest taki duży:

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

Test jest pokazany poniżej:

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

Nie jestem wielkim znawcą innych języków, ale myślę, że jeśli te języki nie mają takiej funkcjonalności w swoim menu, to nie sposób tego zrobić. Przynajmniej z C ++ możesz, i myślę, że nie jest to tak bardzo skomplikowane ...

sigidagi
źródło
„… i zastosuj te elementy krotki do wariadycznej funkcji szablonu” . Sekcja testowa zawiera jednak tylko funkcje wariadyczne nie będące szablonami. Jeśli dodam template<class ... T> void three(T...) {}coś podobnego i spróbuję użyć Apply na tym, to się nie kompiluje.
Zitrax
32

Uważam, że jest to najbardziej eleganckie rozwiązanie (i jest optymalnie przekazane):

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

Przykładowe użycie:

void foo(int i, bool b);

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

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

Niestety GCC (przynajmniej 4.6) nie kompiluje tego z "przepraszam, nie zaimplementowano: zniekształcone przeciążenie" (co po prostu oznacza, że ​​kompilator nie implementuje jeszcze w pełni specyfikacji C ++ 11), a ponieważ używa szablonów wariadycznych, nie będzie działa w MSVC, więc jest mniej lub bardziej bezużyteczna. Jednak gdy pojawi się kompilator obsługujący specyfikację, będzie to najlepsze podejście IMHO. (Uwaga: nie jest trudno to zmodyfikować, aby można było obejść braki w GCC lub zaimplementować go z Boost Preprocessor, ale psuje elegancję, więc to jest wersja, którą publikuję).

GCC 4.7 obsługuje teraz ten kod dobrze.

Edycja: Dodano przekierowanie wokół rzeczywistego wywołania funkcji, aby wspierać formularz odniesienia rvalue * w przypadku, gdy używasz clang (lub jeśli ktoś inny rzeczywiście zacznie go dodawać).

Edycja: Dodano brakujące przejście do przodu wokół obiektu funkcji w treści funkcji niebędącej składnikiem. Podziękowania dla pheedbaq za wskazanie, że go brakowało.

Edycja: A oto wersja C ++ 14 tylko dlatego, że jest o wiele ładniejsza (w rzeczywistości jeszcze się nie kompiluje):

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

Oto wersja dla funkcji składowych (niezbyt testowana!):

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);
}
DRayX
źródło
1
+1 z wymienionych odpowiedzi, to Twoja była najbliższa mi do pracy z argumentami, których argumenty są wektorami ... ... ale nadal otrzymuję błędy kompilacji. ideone.com/xH5kBH Jeśli skompilujesz to z -DDIRECT_CALL i uruchomisz, zobaczysz, jakie powinno być wyjście. W przeciwnym razie otrzymuję błąd kompilacji (myślę, że decltype nie jest wystarczająco inteligentny, aby zrozumieć mój specjalny przypadek), z gcc 4.7.2.
kfmfe04
3
Wersja gcc na ideaone jest zbyt stara, aby to się udało, nie obsługuje przeciążenia zniekształconego zwracanego typu decltype. Przetestowałem ten kod stosunkowo dokładnie w gcc 4.7.2 i nie napotkałem żadnych problemów. Dzięki gcc 4.8 możesz użyć nowej funkcji automatycznego zwracania wartości w C ++ 17, aby uniknąć wszystkich nieprzyjemnych końcowych typów zwracanych wartości decltype.
DRayX,
1
Z ciekawości, applydlaczego w funkcji niebędącej składową fnie jest opakowana std::forwardwywołaniem, tak jak w typie zwracanym? Czy to nie jest potrzebne?
Brett Rossier
3
Z ciekawości spróbowałem skompilować to w GCC 4.8 i foo('x', true)skompilowałem dokładnie do tego samego kodu asemblera, co apply(foo, ::std::make_tuple('x', true))z każdym poziomem optymalizacji poza -O0.
DRayX,
2
Dzięki C ++ 14 integer_sequenceotrzymujesz nawet prawie poprawną implementację apply()w jego przykładzie. zobacz moją odpowiedź poniżej.
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());
}

Jest to adaptowane z wersji roboczej C ++ 14 przy użyciu index_sequence. Mógłbym zaproponować zastosowanie w przyszłej normie (TS).

PeterSom
źródło
1

Wiadomości nie wyglądają dobrze.

Po przeczytaniu właśnie opublikowanej wersji roboczej standardu nie widzę wbudowanego rozwiązania tego problemu, co wydaje się dziwne.

Najlepszym miejscem do spytania o takie rzeczy (jeśli jeszcze tego nie zrobiłeś) jest comp.lang.c ++. Moderated, ponieważ niektórzy ludzie zajmują się tam regularnie redagowaniem standardowego postu.

Jeśli sprawdzisz ten wątek , ktoś ma to samo pytanie (może to ty, w takim przypadku cała odpowiedź będzie dla ciebie trochę frustrująca!) I sugerowanych jest kilka brzydkich implementacji.

Zastanawiałem się tylko, czy prościej byłoby sprawić, by funkcja akceptowała a tuple, ponieważ konwersja w ten sposób jest łatwiejsza. Ale to implikuje, że wszystkie funkcje powinny akceptować krotki jako argumenty, dla maksymalnej elastyczności, a to po prostu demonstruje dziwność braku wbudowanego rozwinięcia krotki do pakietu argumentów funkcji.

Aktualizacja: powyższy link nie działa - spróbuj wkleić to:

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

Daniel Earwicker
źródło
Zastanawiam się, dlaczego w ogóle zawracają sobie głowę oddzielnymi pojęciami krotki i pakietu argumentów funkcji. Może w zgodnym kompilatorze są one wymienne, ale nigdzie nie zauważyłem wskazania, że ​​o nich czytałem.
Daniel Earwicker,
2
Ponieważ tuple <int, char, string> jest potrzebny jako oddzielny typ; podobnie jak możliwość stworzenia funkcji, która nie wymaga make_type w środku każdego wywołania.
coppro
1
Najlepszym miejscem nie jest też moderacja comp.lang.c ++. Pytania dotyczące C ++ 1x są prawie zawsze lepiej kierowane do comp.std.c ++.
coppro
1

Wszystkie te implementacje są dobre. Ale ze względu na użycie wskaźnika do funkcji składowej kompilator często nie może wbudować wywołania funkcji docelowej (przynajmniej gcc 4.8 nie może, bez względu na wszystko Dlaczego gcc nie może wbudować wskaźników funkcji, które można określić? )

Ale sytuacja się zmienia, jeśli wyślesz wskaźnik do funkcji składowej jako argumenty szablonu, a nie jako parametry funkcji:

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

I zastosowanie:

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

Dowód na inlinable http://goo.gl/5UqVnC


Przy niewielkich zmianach możemy „przeciążać” 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....)

Dodatkowo jest to jedyne rozwiązanie, które działa z funkcjami szablonowymi.

wieża120
źródło
1

1) jeśli masz gotową strukturę parameter_pack jako argument funkcji, możesz po prostu użyć std :: tie w następujący sposób:

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) jeśli nie masz gotowego argumentu parampack, będziesz musiał rozwinąć krotkę w ten sposób

#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);
}
sdd
źródło
0

Co powiesz na to:

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

run_tupleSzablon funkcja przyjmuje daną krotki i przekazać jego elementy indywidualnie do danej funkcji. Wykonuje swoją pracę, rekurencyjnie wywołując szablony funkcji pomocniczych explode_tuple. Ważne jest, aby run_tupleprzekazać rozmiar krotki do explode_tuple; ta liczba działa jako licznik liczby elementów do wyodrębnienia.

Jeśli krotka jest pusta, run_tuplewywołuje pierwszą wersję explode_tuplez funkcją zdalną jako jedynym innym argumentem. Funkcja zdalna jest wywoływana bez argumentów i gotowe. Jeśli krotka nie jest pusta, wyższa liczba jest przekazywana do drugiej wersji explode_tuple, wraz z funkcją remote. Rekurencyjne wywołanieexplode_tuplejest tworzony z tymi samymi argumentami, z wyjątkiem tego, że numer licznika jest zmniejszany o jeden i (odwołanie do) ostatniego elementu krotki jest dołączany jako argument po funkcji zdalnej. W wywołaniu rekurencyjnym albo licznik nie jest zerem, a inne wywołanie jest wykonywane z licznikiem zmniejszonym ponownie, a następny element bez odniesienia jest wstawiany na listę argumentów po funkcji zdalnej, ale przed innymi wstawionymi argumentami, lub licznik osiąga zero i wywoływana jest funkcja zdalna ze wszystkimi argumentami zgromadzonymi po niej.

Nie jestem pewien, czy mam poprawną składnię wymuszania określonej wersji szablonu funkcji. Myślę, że można użyć wskaźnika do funkcji jako obiektu funkcji; kompilator automatycznie to naprawi.

CTMacUser
źródło
0

Oceniam MSVS 2013RC i w niektórych przypadkach nie udało mi się skompilować niektórych z poprzednich rozwiązań proponowanych tutaj. Na przykład MSVS nie skompiluje zwrotów „auto”, jeśli będzie zbyt wiele parametrów funkcji, z powodu limitu nasycenia przestrzeni nazw (wysłałem tę informację do firmy Microsoft w celu poprawienia). W innych przypadkach potrzebujemy dostępu do zwrotu funkcji, chociaż można to również zrobić za pomocą lamdy: poniższe dwa przykłady dają ten sam wynik ..

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

I jeszcze raz dziękuję tym, którzy przede mną opublikowali odpowiedzi, bez tego nie doszedłbym do tego ... więc oto:

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));
}
lap777
źródło
Dlaczego uczynisz argument obiektu wskaźnikiem do stałej? Nie odniesienie, nie stałe odniesienie, a nie tylko wskaźnik? A jeśli funkcja wywoływana nie zadziała const?
wieża 120
0

Rozszerzając rozwiązanie @ David, możesz napisać szablon rekurencyjny, który

  1. Nie używa (nadmiernie rozwlekłej, imo) integer_sequencesemantyki
  2. Nie używa dodatkowego tymczasowego parametru szablonu int Ndo liczenia iteracji rekurencyjnych
  3. (Opcjonalnie dla funktorów statycznych / globalnych) używa funktora jako parametru szablonu do optymalizacji czasu kompilacji

Na przykład:

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

Alternatywnie, jeśli twój funktor nie jest zdefiniowany w czasie kompilacji (np. constexprInstancja niebędąca funktorem lub wyrażenie lambda), możesz użyć go jako parametru funkcji zamiast parametru szablonu klasy i faktycznie całkowicie usunąć klasę zawierającą:

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

W przypadku wywołań funkcji wskaźnika do elementu członkowskiego możesz dostosować dowolny z powyższych fragmentów kodu podobnie jak w odpowiedzi @ David.

Wyjaśnienie

W odniesieniu do drugiego fragmentu kodu istnieją dwie funkcje szablonu: pierwsza pobiera funktor func, krotkę tz typami T...i pakiet parametrów argsz typami Args_tmp.... Gdy jest wywoływana, rekurencyjnie dodaje obiekty od tdo pakietu parametrów po jednym naraz, od początku ( 0) do końca, i wywołuje funkcję ponownie z nowym zwiększonym pakietem parametrów.

Podpis drugiej funkcji jest prawie identyczny z pierwszym, z wyjątkiem tego, że używa typu T...dla pakietu parametrów args. Tak więc, gdy argspierwsza funkcja zostanie całkowicie wypełniona wartościami z t, jej typ będzie T...(w pseudo-kodzie, typeid(T...) == typeid(Args_tmp...)), a zatem kompilator zamiast tego wywoła drugą przeciążoną funkcję, która z kolei wywoła func(args...).

Kod w przykładzie statycznego funktora działa identycznie, z funktorem zamiast tego używanym jako argument szablonu klasy.

CrepeGoat
źródło
wszelkie komentarze na temat optymalizacji pierwszej opcji w czasie kompilacji będą mile widziane, więc mogę uzupełnić moją odpowiedź (i może nauczyć się czegoś nowego).
CrepeGoat
-3

Dlaczego po prostu nie zawinąć argumentów wariadycznych w klasę krotek, a następnie użyć rekursji czasu kompilacji (patrz link ) w celu pobrania indeksu, który Cię interesuje. Uważam, że rozpakowywanie szablonów wariadycznych do kontenera lub kolekcji może nie być typem bezpiecznym dla typów heterogenicznych

template<typename... Args>
auto get_args_as_tuple(Args... args) -> std::tuple<Args...> 
{
    return std::make_tuple(args);
}
outro56
źródło
6
Pytanie było na odwrót. Nie Args...-> tuple, ale tuple-> Args....
Xeo
-4

To proste rozwiązanie działa dla mnie:

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
}
maciek gajewski
źródło