Bagaimana cara memperluas tupel ke dalam argumen fungsi template variadic?


145

Pertimbangkan kasus fungsi templated dengan argumen template variadic:

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

Sekarang, saya memiliki tupel tnilai. Bagaimana cara memanggil func()menggunakan nilai tupel sebagai argumen? Saya telah membaca tentang bind()objek fungsi, dengan call()fungsi, dan juga apply()fungsi di beberapa dokumen yang sekarang sudah usang. Implementasi GNU GCC 4.4 tampaknya memiliki call()fungsi di bind()kelas, tetapi hanya ada sedikit dokumentasi tentang subjek tersebut.

Beberapa orang menyarankan peretasan rekursif yang ditulis tangan, tetapi nilai sebenarnya dari argumen templat variadic adalah dapat menggunakannya dalam kasus seperti di atas.

Adakah yang punya solusi untuk itu, atau petunjuk di mana membacanya?


5
Standar C ++ 14 memiliki solusinya, lihat; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen

1
Idenya adalah untuk membongkar tupel dalam satu ledakan variadic, menggunakan integer_sequence, lihat en.cppreference.com/w/cpp/utility/integer_sequence
Skeen

6
Memiliki integer_sequence S, Anda cukup memanggil fungsi Anda sebagai func(std::get<S>(tuple)...), dan biarkan kompilator menangani sisanya.
Skeen

2
Jika menggunakan C ++ 17 atau lebih baru, abaikan jawaban ini dan lihat yang di bawah ini menggunakan std :: apply
lewis

Jawaban:


47

Ini kode saya jika ada yang tertarik

Pada dasarnya pada waktu kompilasi kompilator akan secara rekursif membuka gulungan semua argumen dalam berbagai panggilan fungsi inklusif <N> -> panggilan <N-1> -> panggilan ... -> panggilan <0> yang merupakan yang terakhir dan kompilator akan mengoptimalkannya. berbagai pemanggilan fungsi menengah untuk hanya menyimpan yang terakhir yang setara dengan func (arg1, arg2, arg3, ...)

Tersedia 2 versi, satu untuk fungsi yang dipanggil pada objek dan yang lainnya untuk fungsi statis.

#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
Apakah mungkin untuk mengadaptasi ini untuk bekerja dalam kasus di mana "fungsi" yang dimaksud sebenarnya adalah konstruktor?
Komandan Tinggi4

Bisakah Anda memberikan contoh tentang apa yang ingin Anda lakukan dan kami dapat melanjutkan dari sana.
David

Solusi ini hanya menyediakan overhead waktu kompilasi dan pada akhirnya akan disederhanakan menjadi (pObj -> * f) (arg0, arg, 1, ... argN); Baik?
Goofy

ya, kompilator akan memampatkan pemanggilan beberapa fungsi menjadi yang terakhir seolah-olah Anda telah menulisnya sendiri yang merupakan keindahan dari semua hal pemrograman meta ini.
David

semua tr1hal dapat diambil sekarang dengan c ++ 11
Ryan Haining

40

Di C ++ 17 Anda dapat melakukan ini:

std::apply(the_function, the_tuple);

Ini sudah berfungsi di Clang ++ 3.9, menggunakan std :: eksperimental :: apply.

Menanggapi komentar yang mengatakan bahwa ini tidak akan berfungsi jika the_functiondiberi template, berikut ini adalah solusinya:

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

Solusi ini adalah solusi yang disederhanakan untuk masalah umum dalam melewatkan set kelebihan beban dan templat fungsi di mana fungsi diharapkan. Solusi umum (yang menangani penerusan sempurna, ketegasan, dan noexcept) disajikan di sini: https://blog.tartanllama.xyz/passing-overload-sets/ .


Menurut kode contoh di std :: apply itu tampaknya tidak berfungsi jika the_functiondiberi template.
Zitrax

2
@Zitrax Anda dapat menentukan argumen template fungsi:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Erbureth mengatakan Reinstate Monica

Ini adalah solusi paling sederhana dan paling elegan. Dan itu berhasil. Terima kasih banyak, M. Alaggan !!!!!! +100 suara
Elliott

36

Di C ++ ada banyak cara untuk memperluas / membongkar tupel dan menerapkan elemen tupel tersebut ke fungsi template variadic. Berikut adalah kelas pembantu kecil yang membuat array indeks. Ini banyak digunakan dalam metaprogramming template:

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

Sekarang kode yang melakukan pekerjaan itu tidak terlalu besar:

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

Tes ditunjukkan di bawah ini:

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

Saya bukan ahli besar dalam bahasa lain, tetapi saya rasa jika bahasa-bahasa ini tidak memiliki fungsi seperti itu di menu mereka, tidak ada cara untuk melakukan itu. Setidaknya dengan C ++ Anda bisa, dan menurut saya tidak terlalu rumit ...


"... dan terapkan elemen tupel tersebut ke fungsi template variadic" . Bagian pengujian hanya berisi fungsi variadic non template. Jika saya menambahkan satu suka template<class ... T> void three(T...) {}dan mencoba menggunakan apply yang tidak dapat dikompilasi.
Zitrax

32

Menurut saya ini adalah solusi paling elegan (dan diteruskan secara optimal):

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

Contoh penggunaan:

void foo(int i, bool b);

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

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

Sayangnya GCC (setidaknya 4.6) gagal untuk mengkompilasi ini dengan "sorry, unimplemented: mangling overload" (yang berarti kompilator belum sepenuhnya mengimplementasikan spesifikasi C ++ 11), dan karena ia menggunakan template variadic, ia tidak akan bekerja di MSVC, jadi kurang lebih tidak berguna. Namun, begitu ada kompiler yang mendukung spesifikasi tersebut, itu akan menjadi IMHO pendekatan terbaik. (Catatan: tidak sulit untuk memodifikasi ini sehingga Anda dapat mengatasi kekurangan di GCC, atau menerapkannya dengan Boost Preprocessor, tetapi merusak keanggunan, jadi ini adalah versi yang saya posting.)

GCC 4.7 sekarang mendukung kode ini dengan baik.

Edit: Ditambahkan maju sekitar panggilan fungsi aktual untuk mendukung formulir referensi rvalue * ini jika Anda menggunakan dentang (atau jika ada orang lain yang benar-benar berkeliling untuk menambahkannya).

Edit: Menambahkan maju yang hilang di sekitar objek fungsi di badan fungsi penerapan non-anggota. Terima kasih kepada pheedbaq karena telah menunjukkan bahwa itu hilang.

Sunting: Dan inilah versi C ++ 14 karena jauh lebih baik (belum benar-benar dikompilasi):

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

Ini adalah versi untuk fungsi anggota (tidak banyak diuji!):

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

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

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

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

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

MyClass mc;

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

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

1
+1 dari jawaban yang terdaftar, jawaban Anda adalah yang paling mendekati saya bisa bekerja dengan argumen yang argumennya adalah vektor ... ... tapi saya masih mendapatkan kesalahan kompilasi. ideone.com/xH5kBH Jika Anda mengkompilasi ini dengan -DDIRECT_CALL dan menjalankannya, Anda akan melihat seperti apa hasilnya. Jika tidak, saya mendapatkan kesalahan kompilasi (menurut saya dectype tidak cukup pintar untuk mengetahui kasus khusus saya), dengan gcc 4.7.2.
kfmfe04

3
Versi gcc di ideaone terlalu tua untuk diteruskan, ini tidak mendukung overloading tipe pengembalian deklarasi yang rusak. Saya telah menguji kode ini secara relatif menyeluruh di gcc 4.7.2, dan saya tidak mengalami masalah apa pun. Dengan gcc 4.8, Anda dapat menggunakan fitur nilai pengembalian otomatis C ++ 17 yang baru untuk menghindari semua jenis pengembalian jejak deklarasi yang buruk.
DRayX

1
Karena penasaran, dalam fungsi non-anggota apply, mengapa ftidak dibungkus dengan std::forwardpanggilan, seperti pada tipe kembalian? Apakah tidak dibutuhkan?
Brett Rossier

3
Karena penasaran, saya mencoba mengkompilasi ini di GCC 4.8, dan foo('x', true)mengkompilasi ke kode assembly yang sama persis apply(foo, ::std::make_tuple('x', true))dengan tingkat pengoptimalan apa pun selain -O0.
DRayX

2
Dengan C ++ 14 integer_sequenceAnda bahkan mendapatkan implementasi yang hampir benar apply()dalam contohnya. lihat jawaban saya di bawah ini.
PeterSom

30
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());
}

Ini diadaptasi dari draf C ++ 14 menggunakan index_sequence. Saya mungkin mengusulkan untuk menerapkan standar masa depan (TS).


1

Beritanya tidak terlihat bagus.

Setelah membaca draf standar yang baru saja dirilis , saya tidak melihat solusi bawaan untuk ini, yang memang tampak aneh.

Tempat terbaik untuk bertanya tentang hal-hal seperti itu (jika Anda belum melakukannya) adalah comp.lang.c ++. Dimoderatori, karena beberapa orang terlibat dalam penyusunan posting standar di sana secara teratur.

Jika Anda memeriksa utas ini , seseorang memiliki pertanyaan yang sama (mungkin itu Anda, dalam hal ini Anda akan menemukan jawaban keseluruhan ini sedikit membuat frustrasi!), Dan beberapa penerapan yang sangat buruk disarankan.

Saya hanya bertanya-tanya apakah akan lebih sederhana untuk membuat fungsi menerima a tuple, karena konversi dengan cara itu lebih mudah. Tapi ini menyiratkan bahwa semua fungsi harus menerima tupel sebagai argumen, untuk fleksibilitas maksimum, dan sehingga hanya menunjukkan keanehan karena tidak menyediakan perluasan paket argumen tupel ke fungsi bawaan.

Perbarui: tautan di atas tidak berfungsi - coba tempel ini:

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


Saya bertanya-tanya mengapa mereka bahkan repot-repot memiliki gagasan terpisah tentang tuple dan paket argumen fungsi. Mungkin dalam kompiler yang sesuai, mereka dapat dipertukarkan tetapi saya belum melihat indikasi itu di mana pun saya membaca tentang mereka.
Daniel Earwicker

2
Karena tuple <int, char, string> diperlukan sebagai tipe terpisah; seperti kemampuan untuk membuat fungsi yang tidak memerlukan make_type di tengah setiap panggilan.
coppro

1
Selain itu, tempat terbaik bukanlah comp.lang.c ++. Dimoderasi. Pertanyaan tentang C ++ 1x hampir selalu lebih baik diarahkan ke comp.std.c ++.
coppro

1

Semua implementasi ini bagus. Tetapi karena penggunaan pointer ke compiler fungsi anggota sering tidak dapat menyebariskan panggilan fungsi target (setidaknya gcc 4.8 tidak bisa, apa pun yang terjadi, Mengapa gcc tidak bisa mengarahkan pointer fungsi yang dapat ditentukan? )

Tetapi hal-hal berubah jika mengirim penunjuk ke fungsi anggota sebagai argumen templat, bukan sebagai parameter fungsi:

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

Dan penggunaan:

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

Bukti http://goo.gl/5UqVnC inlinable


Dengan perubahan kecil, kita dapat "membebani" 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....)

Ditambah ini adalah satu-satunya solusi yang berfungsi dengan fungsi template.


1

1) jika Anda memiliki struktur parameter_pack readymade sebagai argumen fungsi, Anda dapat menggunakan std :: tie seperti ini:

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) jika Anda tidak memiliki argumen parampack yang sudah jadi, Anda harus melepaskan tupel seperti ini

#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

Bagaimana dengan ini:

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

The run_tuplefungsi template mengambil tupel yang diberikan dan lulus elemen individual untuk fungsi yang diberikan. Ia menjalankan tugasnya dengan memanggil template fungsi helpernya secara rekursif explode_tuple. Penting untuk run_tuplemeneruskan ukuran tupel ke explode_tuple; angka itu bertindak sebagai penghitung berapa banyak elemen yang akan diekstrak.

Jika tupel kosong, maka run_tuplememanggil versi pertama explode_tupledengan fungsi jarak jauh sebagai satu-satunya argumen lainnya. Fungsi jarak jauh dipanggil tanpa argumen dan kami selesai. Jika tupel tidak kosong, angka yang lebih tinggi akan diteruskan ke versi kedua explode_tuple, bersama dengan fungsi jarak jauh. Panggilan rekursif keexplode_tupledibuat, dengan argumen yang sama, kecuali nomor counter dikurangi satu dan (referensi ke) elemen tupel terakhir ditempelkan sebagai argumen setelah fungsi jarak jauh. Dalam panggilan rekursif, baik penghitung bukan nol, dan panggilan lain dibuat dengan penghitung dikurangi lagi dan elemen berikutnya-tidak direferensikan disisipkan dalam daftar argumen setelah fungsi jarak jauh tetapi sebelum argumen yang disisipkan lainnya, atau penghitung mencapai nol dan fungsi jarak jauh dipanggil dengan semua argumen yang dikumpulkan setelahnya.

Saya tidak yakin saya memiliki sintaks untuk memaksa versi tertentu dari template fungsi dengan benar. Saya pikir Anda dapat menggunakan pointer-to-function sebagai objek fungsi; kompiler akan secara otomatis memperbaikinya.


0

Saya sedang mengevaluasi MSVS 2013RC, dan gagal mengumpulkan beberapa solusi sebelumnya yang diusulkan di sini dalam beberapa kasus. Misalnya, MSVS akan gagal untuk mengkompilasi pengembalian "otomatis" jika ada terlalu banyak parameter fungsi, karena batas imbrication namespace (saya mengirim info itu ke Microsoft untuk memperbaikinya). Dalam kasus lain, kita memerlukan akses ke fungsi yang dikembalikan, meskipun itu juga dapat dilakukan dengan lamda: dua contoh berikut memberikan hasil yang sama ..

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

Dan sekali lagi terima kasih kepada mereka yang memposting jawaban di sini sebelum saya, saya tidak akan mendapatkan ini tanpanya ... jadi ini dia:

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

Mengapa Anda membuat argumen objek sebagai pointer const? Bukan referensi, bukan referensi const, bukan hanya pointer? Bagaimana jika fungsi yang dapat dipanggil tidak const?
tower120

0

Memperluas solusi @ David, Anda dapat menulis template rekursif itu

  1. Tidak menggunakan integer_sequencesemantik (terlalu bertele-tele, imo)
  2. Tidak menggunakan parameter template sementara tambahan int Nuntuk menghitung iterasi rekursif
  3. (Opsional untuk fungsi statis / global) menggunakan functor sebagai parameter template untuk pengoptimalan waktu kompilasi

Misalnya:

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

Alternatifnya jika functor Anda tidak ditentukan pada waktu kompilasi (misalnya, constexprturunan non- functor, atau ekspresi lambda), Anda dapat menggunakannya sebagai parameter fungsi alih-alih parameter templat kelas, dan pada kenyataannya menghapus seluruh kelas yang memuatnya:

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

Untuk callable pointer-to-member-function, Anda dapat menyesuaikan salah satu dari potongan kode di atas sama seperti dalam jawaban @ David.

Penjelasan

Mengacu pada potongan kode kedua, ada dua fungsi template: yang pertama mengambil functor func, tuple tdengan tipe T..., dan paket parameter argstipe Args_tmp.... Ketika dipanggil, itu secara rekursif menambahkan objek dari tke paket parameter satu per satu, dari awal ( 0) ke akhir, dan memanggil fungsi lagi dengan paket parameter incremented baru.

Tanda tangan fungsi kedua hampir identik dengan yang pertama, kecuali fungsi ini menggunakan tipe T...untuk paket parameter args. Jadi, setelah argsfungsi pertama terisi penuh dengan nilai dari t, tipenya akan menjadi T...(dalam psuedo-code, typeid(T...) == typeid(Args_tmp...)), dan dengan demikian compiler akan memanggil fungsi kedua yang kelebihan beban, yang pada gilirannya akan memanggil func(args...).

Kode dalam contoh fungsi statis bekerja sama, dengan functor sebagai gantinya digunakan sebagai argumen template kelas.


komentar apa pun tentang pengoptimalan waktu kompilasi dari opsi pertama akan dihargai, jadi saya dapat membuat jawaban saya lebih lengkap (dan mungkin mempelajari sesuatu yang baru).
CrepeGoat

-3

Mengapa tidak hanya membungkus argumen variadic Anda ke dalam kelas tuple dan kemudian menggunakan rekursi waktu kompilasi (lihat tautan ) untuk mengambil indeks yang Anda minati. Saya menemukan bahwa membongkar template variadic ke dalam wadah atau koleksi mungkin bukan tipe aman wrt tipe heterogen

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

6
Pertanyaannya adalah sebaliknya. Bukan Args...-> tuple, tapi tuple-> Args....
Xeo

-4

Solusi sederhana ini berhasil untuk saya:

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
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.