2015-08-15 3 views
9

ich einen Code haben, die Anordnung für eine JIT Idee erzeugt arbeite ich an. Ich benutze Metaprogrammierung, um Aufrufe zu generieren, indem ich den Funktionstyp analysiere und dann die richtige Assembly erzeuge, um sie aufzurufen. Ich wollte vor kurzem Lambda-Unterstützung hinzufügen, und Lambdas haben zwei Versionen, nicht erfassen (normale __cdecl Funktionsaufruf) und erfassen (__thiscall, Member-Function-Aufruf mit dem Lambda-Objekt als Kontext).erkennen, wenn C++ Lambda kann umgewandelt werden Zeiger auf Funktion

__thiscall ist etwas teurer, so würde Ich mag es zu vermeiden, wann immer möglich, und ich würde auch auf dem Lambda-Typ abhängig unterschiedliche Anruferzeugungsfunktionen zu vermeiden, wie zu verwenden.

Ich habe versucht, viele Möglichkeiten, um die Lambda-Typ über Templates und SFINAE und alle sind gescheitert zu erkennen.

Nicht erfassende Lambdas haben eine ::operator function_type*, die man verwenden kann, um sie in Funktionszeiger umzuwandeln, während das Erfassen von Lambda dies nicht tut.

Relevante C++ Spezifikation: http://en.cppreference.com/w/cpp/language/lambda

Irgendwelche Ideen?

bearbeiten Ich mag würde, eine Lösung haben, die für vs 2013/2015 arbeitet, gcc und Klirren

Prüfregeln folgt

#include <utility> 

    //this doesn't work 
    template < class C, class T > 
    struct HasConversion { 
     static int test(decltype(std::declval<C>().operator T*, bool()) bar) { 
      return 1; 
     } 

     static int test(...) { 
      return 0; 
     } 
    }; 

    template <class C> 
    void lambda_pointer(C lambda) { 
     int(*function)() = lambda; 

     printf("Lambda function: %p without context\n", function); 
    } 

    template <class C> 
    void lambda_pointer_ctx(C lambda) { 
     int(C::*function)() const = &C::operator(); 

     void* context = &lambda; 

     printf("Lambda function: %p with context: %p\n", function, context); 
    } 

    int main() { 
     int a; 

     auto l1 = [] { 
      return 5; 
     }; 

     auto l2 = [a] { 
      return a; 
     }; 


     //non capturing case 

     //works as expected 
     lambda_pointer(l1); 

     //works as expected (ctx is meaningless and not used) 
     lambda_pointer_ctx(l1); 



     //lambda with capture (needs context) 

     //fails as expected 
     lambda_pointer(l1); 

     //works as expected (ctx is a pointer to class containing the captures) 
     lambda_pointer_ctx(l1); 

     /* 
     //this doesn't work :< 
     typedef int afunct() const; 

     HasConversion<decltype(l1), afunct>::test(0); 
     HasConversion<decltype(l2), afunct>::test(0); 
     */ 


     return 0; 
    } 
+0

Kennen Sie die Unterschrift des Lambda? Das macht es ein bisschen sauberer, wenn Sie es tun. – Yakk

+0

Wenn Sie die Signatur kannten, könnten Sie 'std :: is_assignable {}', oder in Ihrem Fall 'std :: is_assignable {} 'mit' typedef void afunct (int); ' –

+0

Nein, das muss für jede Signatur funktionieren - ich folge dem Aufruf, der aus der Signatur generiert werden soll –

Antwort

5

Wenn Sie die Signatur einer Funktion kennen Sie Ihre Lambda wollen umgewandelt werden, können Sie die nutzen std::is_assignable Merkmal:

auto lambda = [] (char, double) -> int { return 0; }; 
using signature = int(char, double); 
static_assert(std::is_assignable<signature*&, decltype(lambda)>::value, "!"); 

DEMO

So kann es auch mit generischen Lambdas arbeiten.


Ich möchte eine Lösung, die für vs 2013/2015, gcc und Klirren arbeitet

Wenn Sie die Signatur nicht wissen, hier ist ein Ansatz, der ist eine Alternative zur Überprüfung, ob + eine implizite Konvertierung auslöst. Dieser nutzt den std::is_assignable Test aus und überprüft, ob ein Lambda einem Funktionszeiger mit der gleichen Signatur wie der Funktionsaufrufoperator des Lambdas zuweisbar ist. (Genau wie ein Test mit unary operator plus, funktioniert das nicht mit generischen lambdas. In C++ 11 gibt es jedoch keine generischen lambdas).

#include <type_traits> 

template <typename T> 
struct identity { using type = T; }; 

template <typename...> 
using void_t = void; 

template <typename F> 
struct call_operator; 

template <typename C, typename R, typename... A> 
struct call_operator<R(C::*)(A...)> : identity<R(A...)> {}; 

template <typename C, typename R, typename... A> 
struct call_operator<R(C::*)(A...) const> : identity<R(A...)> {}; 

template <typename F> 
using call_operator_t = typename call_operator<F>::type; 

template <typename, typename = void_t<>> 
struct is_convertible_to_function 
    : std::false_type {}; 

template <typename L> 
struct is_convertible_to_function<L, void_t<decltype(&L::operator())>> 
    : std::is_assignable<call_operator_t<decltype(&L::operator())>*&, L> {}; 

Test:

int main() 
{ 
    auto x = [] { return 5; }; 
    auto y = [x] { return x(); }; 

    static_assert(is_convertible_to_function<decltype(x)>::value, "!"); 
    static_assert(!is_convertible_to_function<decltype(y)>::value, "!"); 
} 

GCC, VC++, Clang++

+1

Ich endete mit einer ähnlichen (aber weniger gut geschriebenen) Lösung. Akzeptieren wegen der besseren Portabilität. –

2

Nichtauffang lambdas eine sehr interessante Eigenschaft haben, : Sie können sie in einen geeigneten Funktionszeiger konvertieren, aber sie können dies auch implizit tun, wenn Sie ihnen unäre operator + zuweisen. Also:

template<class...> using void_t = void; 

template <class T, class = void> 
struct has_capture : std::true_type {}; 

template <class T> 
struct has_capture<T, void_t<decltype(+std::declval<T>())>> : std::false_type {}; 

int main() { 
    auto f1 = []{}; 
    auto f2 = [&f1]{}; 

    static_assert(!has_capture<decltype(f1)>{}, ""); 
    static_assert(has_capture<decltype(f2)>{}, ""); 
} 
+0

Ärgerlich, eine Version von MSVC brach das: es lieferte mehrere Aufrufkonventionen und unäre '+' wurde mehrdeutig. 2015 Vorveröffentlichung vielleicht? Weiß nicht, wenn es versandt wurde. – Yakk

+0

@Yakk keine Ahnung, aber wenn es stimmt, gut, dass saugt:/ – Quentin

+0

Es ist in der Tat auf VS 2013 gebrochen, die Installation 2015 zu testen –

3

Der HasConversion Ansatz Sie gehen ist ein Überbleibsel aus C++ 03. Die Idee, dort die unterschiedlichen Rückgabetypen der Überlastung von test zu verwenden war (hat man ein char zurückkehren und die andere ein long, zum Beispiel) und prüfen, ob die sizeof() des Rückgabetypen übereinstimmt, was Sie erwarten.

Sobald wir sind auf C++ 11 allerdings gibt es viel bessere Methoden. Wir können zum Beispiel verwenden void_t:

template <typename... > 
using void_t = void; 

eine Art Zug schreiben:

template <typename T, typename = void> 
struct has_operator_plus : std::false_type { }; 

template <typename T> 
struct has_operator_plus<T, 
    void_t<decltype(+std::declval<T>())>> 
: std::true_type { }; 

int main() { 
    auto x = []{ return 5; }; 
    auto y = [x]{ return x(); }; 

    std::cout << has_operator_plus<decltype(x)>::value << std::endl; // 1 
    std::cout << has_operator_plus<decltype(y)>::value << std::endl; // 0 
} 
+3

Kann nicht warten, bis C++ 17: 'Vorlage Konzept bool has_operator_plus = erfordert (T t) { + t;}; ' – Columbo

+0

Leider scheint es, als ob es ein Problem auf vs 2013 (und 2015) gibt, dass es immer 1 zurückgibt. Es funktioniert jedoch richtig, wenn es auf gcc 5.2 getestet wird. Danke für den void_t Trick! Ich bin mit C++ 11-Mustern noch nicht ganz so weit, sogar meine C++ 03 ist ziemlich rostig. –