Apakah mungkin untuk mengetahui tipe parameter dan tipe kembalian lambda?

136

Diberikan lambda, apakah mungkin untuk mengetahui tipe parameter dan tipe kembaliannya? Jika ya, bagaimana caranya?

Pada dasarnya, saya ingin lambda_traitsyang dapat digunakan dengan cara berikut:

auto lambda = [](int i) { return long(i*10); };

lambda_traits<decltype(lambda)>::param_type  i; //i should be int
lambda_traits<decltype(lambda)>::return_type l; //l should be long

Motivasi di baliknya adalah saya ingin menggunakan lambda_traitstemplate fungsi yang menerima lambda sebagai argumen, dan saya perlu mengetahui jenis parameternya dan jenis kembalian di dalam fungsi:

template<typename TLambda>
void f(TLambda lambda)
{
   typedef typename lambda_traits<TLambda>::param_type  P;
   typedef typename lambda_traits<TLambda>::return_type R;

   std::function<R(P)> fun = lambda; //I want to do this!
   //...
}

Untuk saat ini, kita dapat berasumsi bahwa lambda membutuhkan satu argumen.

Awalnya, saya mencoba bekerja dengan std::functionsebagai:

template<typename T>
A<T> f(std::function<bool(T)> fun)
{
   return A<T>(fun);
}

f([](int){return true;}); //error

Namun hal itu jelas akan memberikan error. Jadi saya mengubahnya ke TLambdaversi template fungsi dan ingin membangun std::functionobjek di dalam fungsi (seperti yang ditunjukkan di atas).

Nawaz
sumber
Jika Anda mengetahui tipe parameternya maka ini dapat digunakan untuk mengetahui tipe yang dikembalikan. Saya tidak tahu bagaimana mengetahui jenis parameternya.
Mankarse
Apakah diasumsikan bahwa fungsi membutuhkan satu argumen?
iammilind
1
"parameter type" Tapi fungsi lambda arbitrer tidak memiliki tipe parameter. Ini bisa mengambil sejumlah parameter. Jadi kelas sifat apa pun harus dirancang untuk mengkueri parameter berdasarkan indeks posisi.
Nicol Bolas
@ iammilind: Ya. untuk saat ini, kami dapat berasumsi bahwa.
Nawaz
@NicolBolas: Untuk saat ini, kami dapat berasumsi bahwa lambda mengambil tepat satu argumen.
Nawaz

Jawaban:

163

Lucu, saya baru saja menulis function_traitsimplementasi berdasarkan Mengkhususkan template pada lambda di C ++ 0x yang dapat memberikan tipe parameter. Triknya, seperti yang dijelaskan dalam jawaban dalam pertanyaan itu, adalah dengan menggunakan lambda's . decltypeoperator()

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
    enum { arity = sizeof...(Args) };
    // arity is the number of arguments.

    typedef ReturnType result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
        // the i-th argument is equivalent to the i-th tuple element of a tuple
        // composed of those arguments.
    };
};

// test code below:
int main()
{
    auto lambda = [](int i) { return long(i*10); };

    typedef function_traits<decltype(lambda)> traits;

    static_assert(std::is_same<long, traits::result_type>::value, "err");
    static_assert(std::is_same<int, traits::arg<0>::type>::value, "err");

    return 0;
}

Perhatikan bahwa solusi ini tidak berfungsi untuk lambda umum seperti [](auto x) {}.

kennytm
sumber
Heh, saya baru saja menulis ini. Tidak memikirkannya tuple_element, terima kasih.
GManNickG
@GMan: Jika pendekatan Anda tidak persis sama dengan ini, silakan posting. Saya akan menguji solusi ini.
Nawaz
3
Sifat lengkap juga akan menggunakan spesialisasi untuk non- const, untuk lambda yang dideklarasikan mutable( []() mutable -> T { ... }).
Luc Danton
1
@Andry itu masalah mendasar dengan objek fungsi yang (berpotensi) memiliki beberapa kelebihan beban operator()tidak dengan implementasi ini. autobukan tipe, jadi itu tidak akan pernah menjadi jawaban untuktraits::template arg<0>::type
Caleth
1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/… Sebagai solusi untuk tautan rusak: coba cari dari akarnya, Luke.
Andry
11

Meskipun saya tidak yakin ini benar-benar standar yang sesuai, ideone menyusun kode berikut:

template< class > struct mem_type;

template< class C, class T > struct mem_type< T C::* > {
  typedef T type;
};

template< class T > struct lambda_func_type {
  typedef typename mem_type< decltype( &T::operator() ) >::type type;
};

int main() {
  auto l = [](int i) { return long(i); };
  typedef lambda_func_type< decltype(l) >::type T;
  static_assert( std::is_same< T, long( int )const >::value, "" );
}

Namun, ini hanya menyediakan tipe fungsi, jadi tipe hasil dan parameter harus diekstraksi darinya. Jika Anda dapat menggunakan boost::function_traits, result_typedan arg1_type akan memenuhi tujuannya. Karena ideone tampaknya tidak memberikan dorongan dalam mode C ++ 11, saya tidak dapat memposting kode sebenarnya, maaf.

Ise Wisteria
sumber
1
Saya pikir, ini adalah awal yang baik. 1 untuk itu. Sekarang kita perlu mengerjakan jenis fungsi untuk mengekstrak informasi yang diperlukan. (Saya tidak ingin menggunakan Boost sekarang, karena saya ingin mempelajari hal-hal tersebut).
Nawaz
6

Metode spesialisasi yang ditunjukkan dalam jawaban @KennyTMs dapat diperluas untuk mencakup semua kasus, termasuk lambda variadic dan mutable:

template <typename T>
struct closure_traits : closure_traits<decltype(&T::operator())> {};

#define REM_CTOR(...) __VA_ARGS__
#define SPEC(cv, var, is_var)                                              \
template <typename C, typename R, typename... Args>                        \
struct closure_traits<R (C::*) (Args... REM_CTOR var) cv>                  \
{                                                                          \
    using arity = std::integral_constant<std::size_t, sizeof...(Args) >;   \
    using is_variadic = std::integral_constant<bool, is_var>;              \
    using is_const    = std::is_const<int cv>;                             \
                                                                           \
    using result_type = R;                                                 \
                                                                           \
    template <std::size_t i>                                               \
    using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \
};

SPEC(const, (,...), 1)
SPEC(const, (), 0)
SPEC(, (,...), 1)
SPEC(, (), 0)

Demo .

Perhatikan bahwa arity tidak disesuaikan untuk variadic operator()s. Sebaliknya orang juga bisa mempertimbangkan is_variadic.

Columbo
sumber
1

Jawaban yang diberikan oleh @KennyTMs berfungsi dengan baik, namun jika lambda tidak memiliki parameter, menggunakan indeks arg <0> tidak dapat dikompilasi. Jika ada orang lain yang mengalami masalah ini, saya memiliki solusi sederhana (lebih sederhana daripada menggunakan solusi terkait SFINAE, yaitu).

Tambahkan saja void ke akhir tupel di arg struct setelah tipe argumen variadic. yaitu

template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...,void>>::type type;
    };

karena arity tidak bergantung pada jumlah parameter template yang sebenarnya, yang sebenarnya tidak akan salah, dan jika 0 maka setidaknya arg <0> akan tetap ada dan Anda dapat melakukannya sesuka Anda. Jika Anda sudah berencana untuk tidak melebihi indeks arg<arity-1>maka itu tidak akan mengganggu implementasi Anda saat ini.

Jon Koelzer
sumber