Ist es möglich, den Parametertyp und den Rückgabetyp eines Lambda herauszufinden?

135

Ist es bei einem Lambda möglich, den Parametertyp und den Rückgabetyp herauszufinden? Wenn ja, wie?

Grundsätzlich möchte ich, lambda_traitswelche auf folgende Arten verwendet werden kann:

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

Die Motivation dahinter ist, dass ich lambda_traitsin einer Funktionsvorlage verwenden möchte, die ein Lambda als Argument akzeptiert, und ich muss den Parametertyp und den Rückgabetyp innerhalb der Funktion kennen:

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

Wir können vorerst davon ausgehen, dass das Lambda genau ein Argument hat.

Anfangs habe ich versucht, mit folgenden Elementen zu arbeiten std::function:

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

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

Aber es würde offensichtlich Fehler geben. Also habe ich es in die TLambdaVersion der Funktionsvorlage geändert und möchte das std::functionObjekt innerhalb der Funktion erstellen (wie oben gezeigt).

Nawaz
quelle
Wenn Sie wissen , den Parametertyp dann dies kann den Rückgabetyp , um herauszufinden , verwendet werden. Ich weiß allerdings nicht, wie ich den Parametertyp herausfinden soll.
Mankarse
Wird angenommen, dass die Funktion ein einzelnes Argument akzeptiert?
Iammilind
1
"Parametertyp" Eine beliebige Lambda-Funktion hat jedoch keinen Parametertyp. Es kann eine beliebige Anzahl von Parametern annehmen. Daher müsste jede Merkmalsklasse so konzipiert sein, dass Parameter nach Positionsindizes abgefragt werden.
Nicol Bolas
@iammilind: Ja. das können wir vorerst annehmen.
Nawaz
@NicolBolas: Vorerst können wir davon ausgehen, dass das Lambda genau ein Argument hat.
Nawaz

Antworten:

160

Komisch, ich habe gerade eine function_traitsImplementierung geschrieben , die auf der Spezialisierung einer Vorlage auf einem Lambda in C ++ 0x basiert und die Parametertypen angeben kann. Der Trick, wie in der Antwort in dieser Frage beschrieben, besteht darin, den Lambda zu verwenden . 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;
}

Beachten Sie, dass diese Lösung nicht für generisches Lambda wie funktioniert [](auto x) {}.

kennytm
quelle
Heh, ich habe das gerade geschrieben. Ich habe aber nicht darüber tuple_elementnachgedacht, danke.
GManNickG
@GMan: Wenn Ihr Ansatz nicht genau der gleiche ist, posten Sie ihn bitte dann. Ich werde diese Lösung testen.
Nawaz
3
Ein vollständiges Merkmal würde auch eine Spezialisierung für Nicht- const, für das deklarierte Lambda mutable( []() mutable -> T { ... }) verwenden.
Luc Danton
1
@Andry, das ist ein grundlegendes Problem bei Funktionsobjekten, die (möglicherweise) mehrere Überladungen aufweisen operator(), die bei dieser Implementierung nicht vorhanden sind. autoist kein Typ, also kann es nie die Antwort auf seintraits::template arg<0>::type
Caleth
1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/… Als Lösung für defekte Links: Versuchen Sie, von der Wurzel aus zu suchen, Luke.
Andry
11

Obwohl ich nicht sicher bin, ob dies streng standardkonform ist, hat ideone den folgenden Code kompiliert:

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

Dies liefert jedoch nur den Funktionstyp, sodass die Ergebnis- und Parametertypen daraus extrahiert werden müssen. Wenn Sie verwenden können boost::function_traits, result_typeund arg1_type den Zweck erfüllen. Da ideone im C ++ 11-Modus keinen Boost zu bieten scheint, konnte ich den eigentlichen Code leider nicht veröffentlichen.

Ise Glyzinien
quelle
1
Ich denke, es ist ein guter Anfang. +1 dafür. Jetzt müssen wir am Funktionstyp arbeiten, um die erforderlichen Informationen zu extrahieren. (Ich möchte Boost ab sofort nicht mehr verwenden, da ich die Dinge lernen möchte ).
Nawaz
6

Die in der Antwort von @KennyTMs gezeigte Spezialisierungsmethode kann auf alle Fälle ausgedehnt werden, einschließlich variadischer und veränderlicher Lambdas:

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 .

Beachten Sie, dass die Arität nicht für verschiedene Werte angepasst ist operator(). Stattdessen kann man auch überlegen is_variadic.

Columbo
quelle
1

Die Antwort von @KennyTMs funktioniert hervorragend. Wenn ein Lambda jedoch keine Parameter hat, wird die Verwendung des Indexarguments <0> nicht kompiliert. Wenn jemand anderes dieses Problem hatte, habe ich eine einfache Lösung (einfacher als die Verwendung von SFINAE-bezogenen Lösungen).

Fügen Sie am Ende des Tupels in der Argumentstruktur nach den variadischen Argumenttypen einfach void hinzu. dh

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

Da die Arität nicht von der tatsächlichen Anzahl der Vorlagenparameter abhängt, ist die tatsächliche nicht falsch. Wenn sie 0 ist, ist mindestens arg <0> noch vorhanden, und Sie können damit tun, was Sie wollen. Wenn Sie bereits vorhaben, den Index nicht zu überschreiten arg<arity-1>, sollte dies Ihre aktuelle Implementierung nicht beeinträchtigen.

Jon Koelzer
quelle