2010-04-01 8 views
22

Ich habe eine Traits-Klasse geschrieben, die mir Informationen über die Argumente und den Typ einer Funktion oder eines Funktionsobjekts in C++ 0x extrahiert (getestet mit gcc 4.5.0) . Der allgemeine Fall behandelt Funktionsobjekte:Spezialisierung einer Vorlage auf ein Lambda in C++ 0x

template <typename F> 
struct function_traits { 
    template <typename R, typename... A> 
    struct _internal { }; 

    template <typename R, typename... A> 
    struct _internal<R (F::*)(A...)> { 
     // ... 
    }; 

    typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>; 
}; 

Dann habe ich eine Spezialisierung für Normalfunktionen in globalen Bereichen:

template <typename R, typename... A> 
struct function_traits<R (*)(A...)> { 
    // ... 
}; 

Dies funktioniert gut, ich habe eine Funktion in die Vorlage oder ein Funktionsobjekt passieren kann und es funktioniert:

template <typename F> 
void foo(F f) { 
    typename function_traits<F>::whatever ...; 
} 

int f(int x) { ... } 
foo(f); 

Was, wenn statt eine Funktion oder ein Funktionsobjekt in foo von vorbei, ich einen Lambda-Ausdruck übergeben möge?

foo([](int x) { ... }); 

Das Problem hier ist, dass keine Spezialisierung von function_traits<> gilt. Der C++ 0x-Entwurf besagt, dass der Typ des Ausdrucks ein "eindeutiger unbenannter Klassentyp ohne Union" ist. Das Entwirren des Ergebnisses des Aufrufs von typeid(...).name() für den Ausdruck gibt mir die scheinbar interne GCC-Namenskonvention für das Lambda main::{lambda(int)#1}, nicht etwas, das syntaktisch einen C++ - Typnamen darstellt.

Kurz gesagt, ist es etwas, was ich in die Vorlage auszureizen:

template <typename R, typename... A> 
struct function_traits<????> { ... } 

, die diese Züge Klasse ermöglicht einen Lambda-Ausdruck zu akzeptieren?

+0

Nein. Warum denken Sie, dass Sie so etwas brauchen? – sellibitze

+1

Ich dachte mein Beispiel gab einen anständigen Anwendungsfall: Wenn ich einen generischen Algorithmus habe, der ein Funktions- oder Funktionsobjekt aufnimmt, kann ich diese Merkmalsklasse verwenden, um nicht nur den Rückgabetyp zu bestimmen (was heutzutage auch mit declty möglich wäre), aber auch die Arten der Argumente. (Ich habe den Großteil des Codes weggelassen, um zu verhindern, dass der Post zu lang wird.) Da ich ein Funktions- oder Funktionsobjekt übergeben kann, möchte ich für Orthogonalitätszwecke auch ein Lambda übergeben können. Dies ist alles eine akademische Übung, die aus dem Lesen von "Elements of Programming" entstand. –

+0

@Tony: Die Antwort ist ja, ich habe es getan. Ich werde aber ein wenig später auf diese Frage zurückkommen können. Welche Eigenschaften willst du bekommen? – GManNickG

Antwort

17

Ich denke, es ist möglich, Merkmale für Lambdas zu spezialisieren und Musteranpassung auf die Signatur des unbenannten Funktors. Hier ist der Code, der auf g ++ 4.5 funktioniert. Obwohl es funktioniert, scheint der Mustervergleich auf Lambda der Intuition entgegen zu arbeiten. Ich habe Kommentare inline.

struct X 
{ 
    float operator() (float i) { return i*2; } 
    // If the following is enabled, program fails to compile 
    // mostly because of ambiguity reasons. 
    //double operator() (float i, double d) { return d*f; } 
}; 

template <typename T> 
struct function_traits // matches when T=X or T=lambda 
// As expected, lambda creates a "unique, unnamed, non-union class type" 
// so it matches here 
{ 
    // Here is what you are looking for. The type of the member operator() 
    // of the lambda is taken and mapped again on function_traits. 
    typedef typename function_traits<decltype(&T::operator())>::return_type return_type; 
}; 

// matches for X::operator() but not of lambda::operator() 
template <typename R, typename C, typename... A> 
struct function_traits<R (C::*)(A...)> 
{ 
    typedef R return_type; 
}; 

// I initially thought the above defined member function specialization of 
// the trait will match lambdas::operator() because a lambda is a functor. 
// It does not, however. Instead, it matches the one below. 
// I wonder why? implementation defined? 
template <typename R, typename... A> 
struct function_traits<R (*)(A...)> // matches for lambda::operator() 
{ 
    typedef R return_type; 
}; 

template <typename F> 
typename function_traits<F>::return_type 
foo(F f) 
{ 
    return f(10); 
} 

template <typename F> 
typename function_traits<F>::return_type 
bar(F f) 
{ 
    return f(5.0f, 100, 0.34); 
} 

int f(int x) { return x + x; } 

int main(void) 
{ 
    foo(f); 
    foo(X()); 
    bar([](float f, int l, double d){ return f+l+d; }); 
} 
+0

Danke, das hat den Trick gemacht. Es kam mir nicht in den Sinn, einfach die nicht spezialisierte Vorlage auf die spezialisierte Version zu ketten. Sobald ich es gesehen habe, sieht es unglaublich elegant und offensichtlich aus. –

+0

Ich bin froh, dass es geholfen hat. Es sieht so aus, als ob die Syntax, den Operator() eines Lambda zu erhalten, wie die Member-Funktion einer Klasse ist, Lambda jedoch eine anonyme, freistehende Funktion ist und R (*) (A ...), nicht aber R (C :: *)(EIN...). – Sumant

+2

Ich denke, der Grund dafür könnte sein, dass "C" in diesem Fall ein implementation-definierter Typ wäre, und der Zugriff darauf über ein Template-Argument würde uns erlauben, unsinnige Dinge wie typedef Aliase dafür zu definieren. Da wir mit dem Lambda-Typ nichts machen können, außer es als Funktion zu bezeichnen, macht es mehr Sinn, aus der Sicht des Sprachdesigns zu erzwingen, dass dies die Spezialisierung ist, die zutrifft. –

1

Durch einen Teil der Arbeit zu einer Reihe von Funktionsschablonen anstelle einer Klassenvorlage delegieren, können Sie die relevanten Informationen extrahieren.

Zunächst sollte ich jedoch sagen, dass die relevante Methode eine const Methode ist, für ein Lambda (für eine nicht-Capturing, nicht-generische, nicht Lambda). So werden Sie nicht in der Lage sein, den Unterschied zwischen einem echten Lambda zu sagen, und dies:

struct { 
    int operator() (int) const { return 7; } 
} object_of_unnamed_name_and_with_suitable_method; 

Daher muss ich davon ausgehen, dass Sie nicht „Sonderbehandlung“ wollen für Lambda-Ausdrücke, und Sie wollen nicht testen Wenn ein Typ ein Lambda-Typ ist, und stattdessen möchten Sie einfach den Rückgabetyp und den Typ aller Argumente für jedes Objekt extrahieren, das einfach genug ist. Mit "einfach genug" meine ich zum Beispiel, dass die operator() Methode nicht selbst eine Vorlage ist. Und, für Bonus-Informationen, ein Boolean, um uns zu sagen, ob eine operator() Methode vorhanden und verwendet wurde, im Gegensatz zu einer einfachen alten Funktion.



// First, a convenient struct in which to store all the results: 
template<bool is_method_, bool is_const_method_, typename C, typename R, typename ...Args> 
struct function_traits_results { 
    constexpr static bool is_method = is_method_; 
    constexpr static bool is_const_method = is_const_method_; 
    typedef C class_type; // void for plain functions. Otherwise, 
          // the functor/lambda type 
    typedef R return_type; 
    typedef tuple<Args...> args_type_as_tuple; 
}; 

// This will extract all the details from a method-signature: 
template<typename> 
struct intermediate_step; 
template<typename R, typename C, typename ...Args> 
struct intermediate_step<R (C::*) (Args...)> // non-const methods 
    : public function_traits_results<true, false, C, R, Args...> 
{ 
}; 
template<typename R, typename C, typename ...Args> 
struct intermediate_step<R (C::*) (Args...) const> // const methods 
    : public function_traits_results<true, true, C, R, Args...> 
{ 
}; 


// These next two overloads do the initial task of separating 
// plain function pointers for functors with ::operator() 
template<typename R, typename ...Args> 
function_traits_results<false, false, void, R, Args...> 
function_traits_helper(R (*) (Args...)); 
template<typename F, typename ..., typename MemberType = decltype(&F::operator()) > 
intermediate_step<MemberType> 
function_traits_helper(F); 


// Finally, the actual `function_traits` struct, that delegates 
// everything to the helper 
template <typename T> 
struct function_traits : public decltype(function_traits_helper(declval<T>())) 
{ 
}; 
+0

Wie kann ich diese Merkmale in einer Funktionsvorlage verwenden, die ein Lambda wie void func (T lambda) {} erhält, um eine std :: function zu deklarieren? – barney

2

Der void_t Trick helfen kann. How does `void_t` work?

Es sei denn, Sie haben C++ 17, werden Sie die Definition von void_t umfassen müssen:

template<typename... Ts> struct make_void { typedef void type;}; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

Fügen Sie ein zusätzliches Template-Argument auf die ursprüngliche Vorlage, Verzug geraten zu void:

template <typename T, typename = void> 
struct function_traits; 

Das Merkmal Objekt für einfache Funktionen ist das gleiche wie Sie bereits haben:

template <typename R, typename... A> 
struct function_traits<R (*)(A...)> 
{ 
    using return_type = R; 
    using class_type = void; 
    using args_type = std:: tuple<A...>; 
}; 

Für Nicht-const Methoden:

template <typename R, typename... A> 
struct function_traits<R (*)(A...)> 
{ 
    using return_type = R; 
    using class_type = void; 
    using args_type = std:: tuple<A...>; 
}; 

Sie const Methoden nicht vergessen:

template <typename R, typename C, typename... A> 
struct function_traits<R (C::*)(A...) const> // const 
{ 
    using return_type = R; 
    using class_type = C; 
    using args_type = std:: tuple<A...>; 
}; 

Schließlich ist die wichtige Eigenschaft. Bei einer Klassenart einschließlich Lambda-Typen möchten wir von T zu decltype(&T::operator()) weiterleiten. Wir möchten sicherstellen, dass dieses Merkmal nur für die Typen T verfügbar ist, für die ::operator() verfügbar ist, und das ist, was void_t für uns tut. Um diese Einschränkung zu erzwingen, müssen wir &T::operator() in den Zug Unterschrift irgendwo setzen, damit template <typename T> struct function_traits<T, void_t< decltype(&T::operator())

template <typename T> 
struct function_traits<T, void_t< decltype(&T::operator()) > > 
: public function_traits<   decltype(&T::operator()) > 
{ 
}; 

Operator() -Methode in (nicht mutable, nicht-generic) lambdas const ist, was erklärt, warum wir das brauchen const Vorlage oben.

Aber letztlich ist das sehr restriktiv. Dies funktioniert nicht mit generischen Lambdas oder Objekten mit Vorlagen operator(). Wenn Sie Ihr Design überdenken, finden Sie einen anderen Ansatz, der flexibler ist.

+0

Fehler unter 'nicht-konstante Methoden'. Sieht aus, als hättest du die freie Funktion kopiert. Es soll Signatur wie die unten stehende Konstante haben – kert