È possibile capire il tipo di parametro e il tipo di ritorno di un lambda?


135

Dato un lambda, è possibile capire il tipo di parametro e il tipo restituito? Se si, come?

Fondamentalmente, voglio lambda_traitsche può essere utilizzato nei seguenti modi:

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

La motivazione dietro è che voglio usare lambda_traitsin un modello di funzione che accetta un lambda come argomento, e ho bisogno di sapere il tipo di parametro e il tipo restituito all'interno della funzione:

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!
   //...
}

Per il momento, possiamo supporre che il lambda prenda esattamente un argomento.

Inizialmente, ho provato a lavorare con std::function:

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

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

Ma ovviamente darebbe errore. Quindi l'ho cambiato in TLambdaversione del modello di funzione e voglio costruire l' std::functionoggetto all'interno della funzione (come mostrato sopra).


Se conosci il tipo di parametro, questo può essere usato per capire il tipo di ritorno. Non so come capire il tipo di parametro però.
Mankarse,

Si presume che la funzione prenda un singolo argomento?
iammilind,

1
"tipo di parametro" Ma una funzione lambda arbitraria non ha un tipo di parametro. Potrebbe richiedere un numero qualsiasi di parametri. Quindi qualsiasi classe di tratti dovrebbe essere progettata per interrogare i parametri in base agli indici di posizione.
Nicol Bolas,

@iammilind: Sì. per ora, possiamo supporre che.
Nawaz,

@NicolBolas: per il momento, possiamo supporre che il lambda prenda esattamente un argomento.
Nawaz,

Risposte:


160

Divertente, ho appena scritto function_traitsun'implementazione basata sulla specializzazione di un modello su una lambda in C ++ 0x che può fornire i tipi di parametro. Il trucco, come descritto nella risposta a quella domanda, è usare il lambda . 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;
}

Nota che questa soluzione non funziona per lambda generici come [](auto x) {}.


Heh, stavo solo scrivendo questo. Non ci ho pensato tuple_element, grazie.
GManNickG

@GMan: Se il tuo approccio non è esattamente lo stesso di questo, ti preghiamo di pubblicarlo. Ho intenzione di testare questa soluzione.
Nawaz,

3
Un tratto completo userebbe anche una specializzazione per non- const, per quelli lambda dichiarati mutable( []() mutable -> T { ... }).
Luc Danton,

1
@Andry è un problema fondamentale con gli oggetti funzione che hanno (potenzialmente) sovraccarichi multipli operator()non con questa implementazione. autonon è un tipo, quindi non può mai essere la risposta atraits::template arg<0>::type
Caleth,

1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/… Come soluzione per collegamenti non funzionanti: prova a cercare dalla radice, Luke.
Andry,

11

Anche se non sono sicuro che questo sia strettamente conforme allo standard, ideone ha compilato il seguente codice:

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

Tuttavia, questo fornisce solo il tipo di funzione, quindi i risultati e i tipi di parametro devono essere estratti da esso. Se è possibile utilizzare boost::function_traits, result_typee arg1_type incontrerete lo scopo. Dal momento che ideone sembra non fornire una spinta in modalità C ++ 11, non sono riuscito a pubblicare il codice attuale, mi dispiace.


1
Penso che sia un buon inizio. +1 per quello. Ora dobbiamo lavorare sul tipo di funzione per estrarre le informazioni richieste. (Non voglio usare Boost per ora, perché voglio imparare le cose).
Nawaz,

6

Il metodo di specializzazione mostrato nella risposta di @KennyTM può essere esteso a tutti i casi, inclusi lambda variadici e mutabili:

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 .

Si noti che l'arity non è regolata per i variabili operator(). Invece si può anche considerare is_variadic.


1

La risposta fornita da @KennyTM funziona alla grande, tuttavia se un lambda non ha parametri, usando l'indice arg <0> non si compila. Se qualcun altro ha avuto questo problema, ho una soluzione semplice (più semplice rispetto all'utilizzo delle soluzioni correlate a SFINAE, cioè).

Basta aggiungere il vuoto alla fine della tupla nella struttura arg dopo i tipi di argomento variadico. vale a dire

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

poiché l'arity non dipende dal numero effettivo di parametri del template, l'effettivo non sarà errato, e se è 0 allora almeno arg <0> esisterà ancora e puoi farci quello che vuoi. Se prevedi già di non superare l'indice, arg<arity-1>non dovrebbe interferire con l'implementazione corrente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.