2013-02-13 8 views
16

Il seguente codice è stato compilato con VC++ 2012:Qual è la convenzione di chiamata predefinita di una funzione lambda C++?

void f1(void (__stdcall *)()) 
{} 

void f2(void (__cdecl *)()) 
{} 

void __cdecl h1() 
{} 

void __stdcall h2() 
{} 

int main() 
{ 
    f1(h1); // error C2664 
    f2(h2); // error C2664 

    f1([](){}); // OK 
    f2([](){}); // OK 

    auto fn = [](){}; 

    f1(fn); // OK 
    f2(fn); // OK 
} 

penso che gli errori sono normali ma le OKs sono anormali.

Quindi, le mie domande sono:

  1. Qual è la convenzione di chiamata di una funzione lambda C++?

  2. Come specificare la convenzione di chiamata di una funzione lambda C++?

  3. Se la convenzione di chiamata non è definita, come riciclare correttamente lo spazio di stack dopo aver chiamato una funzione lambda?

  4. Il compilatore genera automaticamente più versioni di una funzione lambda? vale a dire il seguente pseudo-codice:

    [] __stdcall() {};

    [] __cdecl() {}; ecc

+3

Il collegamento a questa domanda sembra utile: http://stackoverflow.com/questions/14169295/how-to-specify-vc11-lambda-calling-convention – jogojapan

+0

'f1' sta usando' __stdcall' ma 'h1' sta usando' __cdecl'; se scambi quelli intorno funziona? – congusbongus

+0

@Cong, Gli errori sono normali e gli OK sono anormali. – xmllmx

risposta

14

Su VC++ 2012, compilatore sceglie la conversione di chiamare automaticamente lambda apolidi (che non ha le variabili di acquisizione) quando si converte "lambda stateless a funzionare puntatore".

MSDN C++11 Features:

Lambda

[...] Inoltre in Visual C++ in Visual Studio 2012, lambda apolidi sono convertibili a funzionare puntatori. [...] (Il Visual C++ in Visual Studio 2012 è anche meglio di così, perché abbiamo reso lambda stateless convertibile a puntatori di funzioni che hanno convenzioni di chiamata arbitrarie.Questo è importante quando si utilizzano API che si aspettano cose come la funzione __stdcall puntatori)


CURA:.

NB: La conversione chiamando è fuori di C++ standard, dipende da altre specifiche, come piattaforma di ABI (Application binary Interface).

Le seguenti risposte si basano sul codice dell'assieme di uscita con /FAs compiler option. Quindi è solo una supposizione, e per favore chiedete a Microsoft maggiori dettagli: P

Q1. Qual è la convenzione di chiamata di una funzione lambda C++?

Q3. Se la convenzione di chiamata non è definita, come riciclare correttamente lo spazio di stack dopo aver chiamato una funzione lambda?

Prima di tutto, C++ lambda (-expression) non è una funzione (né puntatore a funzione), è possibile chiamare operator() per oggetto lambda come una normale funzione di chiamata. E il codice di assembly di output dice che VC++ 2012 genera lambda-body con la conversione di chiamate __thiscall.

Q2. Come specificare la convenzione di chiamata di una funzione lambda C++?

AFAIK, non c'è modo. (Può essere solo __thiscall)

Q4. Il compilatore genera automaticamente più versioni di una funzione lambda? cioè come il seguente pseudo-codice: [...]

Probabilmente No. Il VC++ 2012 lambda-tipo fornisce solo un'implementazione lambda-corpo (void operator()()), ma fornisce più "conversione definita dall'utente funzionamento puntatore "per ogni conversione di chiamata (puntatore della funzione return dell'operatore con il tipo void (__fastcall*)(void), void (__stdcall*)(void) e void (__cdecl*)(void)).

Ecco un esempio;

// input source code 
auto lm = [](){ /*lambda-body*/ }; 

// reversed C++ code from VC++2012 output assembly code 
class lambda_UNIQUE_HASH { 
    void __thiscall operator()() { 
    /* lambda-body */ 
    } 
    // user-defined conversions 
    typedef void (__fastcall * fp_fastcall_t)(); 
    typedef void (__stdcall * fp_stdcall_t)(); 
    typedef void (__cdecl * fp_cdecl_t)(); 
    operator fp_fastcall_t() { ... } 
    operator fp_stdcall_t() { ... } 
    operator fp_cdecl_t() { ... } 
}; 
lambda_UNIQUE_HASH lm; 
+2

+1 per il riferimento (sebbene la risposta non copra tutti gli aspetti della domanda). – jogojapan

+5

@jogojapan: copre praticamente tutto, dal momento che una funzione lambda è in definitiva una funzione membro. E una funzione membro non ha davvero le convenzioni di chiamata. Non nel modo '__cdecl'. Poiché le convenzioni di chiamata sono specifiche della piattaforma, spetta a ciascuna piattaforma decidere come funziona. Microsoft, mostrando un * grado di competenza * scioccante, ha deciso il modo più utile. –

+0

@NicolBolas Ciò che intendo è principalmente che il testo citato non sembra fornire una risposta chiara alla parte 2 della domanda. La descrizione di cui sopra dice che lambdas è normalmente convertibile in tutte le convenzioni di chiamata, ma ciò non implica necessariamente che non ci sia modo di specificare la convenzione se lo si desidera. Sarebbe naturalmente la sintassi VC-specifica. (Non sono sicuro se capisco il tuo punto sulle funzioni dei membri. Lambdas sono sempre funzioni membro .. perché?) – jogojapan

3

Una funzione lambda stateless è ancora una classe, ma una classe che può essere convertita implicitamente in un puntatore di funzione.

Lo standard C++ non copre le convenzioni di chiamata, ma vi sono poche ragioni per cui un lambda senza stato non può creare un wrapper in alcuna convenzione di chiamata che inoltra attraverso il lambda senza stato quando il lambda viene convertito in un puntatore di funzione.

A titolo di esempio, si potrebbe fare questo:

#include <iostream> 

void __cdecl h1() {} 
void __stdcall h2(){} 

// I'm lazy: 
typedef decltype(&h1) cdecl_nullary_ptr; 
typedef decltype(&h2) stdcall_nullary_ptr; 

template<typename StatelessNullaryFunctor> 
struct make_cdecl { 
    static void __cdecl do_it() { 
    StatelessNullaryFunctor()(); 
    } 
}; 
template<typename StatelessNullaryFunctor> 
struct make_stdcall { 
    static void __stdcall do_it() { 
    StatelessNullaryFunctor()(); 
    } 
}; 

struct test { 
    void operator()() const { hidden_implementation(); } 

    operator cdecl_nullary_ptr() const { 
    return &make_cdecl<test>::do_it; 
    } 
    operator stdcall_nullary_ptr() const { 
    return &make_stdcall<test>::do_it; 
    } 
}; 

dove la nostra classe test nullaria apolide può essere convertito in un puntatore sia cdecl e stdcall funzione implicitamente.

La parte importante di questo è che la convenzione di chiamata fa parte del tipo di puntatore della funzione, quindi operator function_type sa quale convenzione di chiamata viene richiesta. E con l'inoltro perfetto, quanto sopra può anche essere efficiente.

+0

Una funzione lambda è una classe? – jogojapan

+4

@jogojapan: Probabilmente dovrebbe dire che un lambda senza stato è ancora un * oggetto *, ma un oggetto che può essere convertito in un puntatore a funzione. –

Problemi correlati