Firma della funzione espressioni come C++ modello di argomenti

Stavo guardando Don Clugston del FastDelegate mini-libreria e ho notato una strana sintattica trucco con la seguente struttura:

TemplateClass< void( int, int ) > Object;

Sembra quasi come se una firma della funzione viene utilizzata come argomento per un modello di istanza di dichiarazione.

Questa tecnica (la cui presenza in FastDelegate è apparentemente a causa di uno Jody Hagins) è stato utilizzato per semplificare la dichiarazione di istanze del modello con un semi-numero arbitrario di parametri del modello.

Di arguzia, che ha permesso a questo qualcosa di simile al seguente:

//A template with one parameter
template<typename _T1>
struct Object1
{
    _T1 m_member1;
};

//A template with two parameters
template<typename _T1, typename _T2>
struct Object2
{
    _T1 m_member1;
    _T2 m_member2;
};

//A forward declaration
template<typename _Signature>
struct Object;

//Some derived types using "function signature"-style template parameters
template<typename _Dummy, typename _T1>
struct Object<_Dummy(_T1)> : public Object1<_T1> {};

template<typename _Dummy, typename _T1, typename _T2>
struct Object<_Dummy(_T1, _T2)> : public Object2<_T1, _T2> {};

//A. "Vanilla" object declarations
Object1<int> IntObjectA;
Object2<int, char> IntCharObjectA;

//B. Nifty, but equivalent, object declarations
typedef void UnusedType;
Object< UnusedType(int) > IntObjectB;
Object< UnusedType(int, char) > IntCharObjectB;

//C. Even niftier, and still equivalent, object declarations
#define DeclareObject( ... ) Object< UnusedType( __VA_ARGS__ ) >
DeclareObject( int ) IntObjectC;
DeclareObject( int, char ) IntCharObjectC;

Nonostante il vero soffio di hackiness, trovo che questo tipo di spoofy emulazione di variadic modello di argomenti per essere abbastanza sconvolgente.

La vera carne di questo trucco sembra essere il fatto che posso passare testuale costrutti come “Type1(Type2, Type3)” come argomenti di modelli. Così qui sono le mie domande: esattamente Come fa il compilatore a interpretare questo costrutto? È una funzione di firma? O è solo un modello di testo tra parentesi è? Se il primo, allora questo implica che qualsiasi funzione arbitraria firma è di tipo valido, per quanto il modello di processore è interessato?

Un follow-up domanda potrebbe essere che dato esempio di codice è un codice valido, perché non il C++ standard permette solo di fare qualcosa di simile, che non compila?

template<typename _T1>
struct Object
{
    _T1 m_member1;
};

//Note the class identifier is also "Object"
template<typename _T1, typename _T2>
struct Object
{
    _T1 m_member1;
    _T2 m_member2;
};

Object<int> IntObject;
Object<int, char> IntCharObject;
InformationsquelleAutor Jeff Lee | 2011-01-09



2 Replies
  1. 42

    Per quanto riguarda la tua prima domanda – che tipo di int(char, float) – questo è un valido C++ è il tipo di una funzione che prende in char e un float e restituisce un int. Si noti che questo è il tipo di funzione reale, non un puntatore a funzione, che sarebbe un int (*) (char, float). Il tipo effettivo di qualsiasi funzione di questo insolito tipo. Per esempio, il tipo di

    void DoSomething() {
        /* ... */
    }

    è void ().

    La ragione per cui questo non viene molto durante la routine di programmazione, è che nella maggior parte dei casi non è possibile dichiarare variabili di questo tipo. Per esempio, questo codice è illegale:

    void MyFunction() { 
        void function() = DoSomething; //Error!
    }

    Tuttavia, un caso in cui si riescono a vedere i tipi di funzione utilizzato per il passaggio di puntatori a funzione di tutto:

    void MyFunction(void FunctionArgument()) {
         /* ... */
    }

    È più comune vedere questo tipo di funzione scritta in un puntatore a funzione, ma è perfettamente bene a prendere la stessa funzione. Si viene proiettati dietro le quinte.

    Per la tua seconda domanda, perché è illegale avere lo stesso modello scritti con numeri diversi di argomenti, non so esattamente la formulazione specifica che vieta, ma ha qualcosa a che fare con il fatto che una volta che hai dichiarato un modello di classe, non è possibile modificare il numero di argomenti ad esso. Tuttavia, è possibile fornire una specializzazione parziale, oltre che di un modello che ha un diverso numero di argomenti, a condizione, naturalmente, che la specializzazione parziale, solo è specializzata sull’originale numero di argomenti. Per esempio:

    template <typename T> class Function;
    template <typename Arg, typename Ret> class Function<Ret (Arg)> { 
        /* ... */
    };

    Qui, Function sempre accetta un parametro. Il modello di specializzazione prende due argomenti, ma la specializzazione è ancora solo su un tipo (in particolare, Ret (Arg)).

    • Molto elegante. L’idea di un “tipo” di una funzione è praticamente al di fuori di giorno-per-giorno C++ concetto di spazio, che è il motivo per cui questo trucco sembrava così strano per me prima. Ma quindi, se “void( int )” è un tipo, si può avere, per esempio, un tipo che è un puntatore a “void( int )”? Sarebbe la stessa cosa come un puntatore a funzione?
    • Sì, è un puntatore a funzione. È void (*)(int).
    • Aha, quindi la “corretta” puntatore a funzione sintassi, ad esempio, “void (*fp)() = & Unafunzione;”. E anche privo di riferimenti “chiamata di funzione(*fp)();” mi sembra un pò scadente per me che ci sono compilatori che consentono di rinunciare all’indirizzo e risolvere gli operatori…rende il tipo di funzione e di funzione di tipo puntatore sembra la stessa cosa, quando a quanto pare non lo sono.
    • Lee – I compilatori in realtà consentono di ottenere via con questo. Il C++ ISO spec (punto 4.3.1) afferma che ogni funzione può essere implicitamente convertito in un puntatore a funzione. Penso che sia per la compatibilità con il C.
    • Non che contraddicono Clugston del reclamo nell’articolo linkato, oppure sta parlando di qualcosa d’altro: “Alcuni compilatori (più in particolare, MSVC 6 e 7) ti consente di omettere l’ &, anche se è non-standard, e confusione. Più conformi allo standard compilatori (ad esempio, GNU G++ e MSVC 8 (un.k.un. VS 2005)) richiedono, così si dovrebbe assolutamente mettere dentro.”
    • Nel contesto di materie prime funzioni non avete bisogno di un &, ma per i puntatori-a-membro-funzione & è necessario. Io non sono sicuro di quello che Clugston significava che l’articolo, ma sembra che era leggermente sbagliato. Solo una supposizione.
    • Sì, il contesto era il metodo di istanza puntatori, così sembra, di fatto, egli è d’accordo con te.
    • Anche se sono d’accordo non si devono usare le macro varargs, ma perché non è VA_ARGS, una parte di C++? E ‘ stato aggiunto in C++11
    • Al momento ho scritto questo, non sapevo che C++11 aveva aggiunto __VA_ARGS__; non era supportato in C++03. Grazie per avermi fatto sapere che… ti aggiorna la risposta di conseguenza.

  2. 4
    int* int_pointer;    //int_pointer   has type "int*"
    int& int_reference;  //int_reference has type "int&"
    int  int_value;      //int_value     has type "int"
    
    void (*function_pointer)(int, int);    //function_pointer has type
                                           //"void (*)(int, int)"
    void (&function_reference)(int, int);  //function_reference has type
                                           //"void (&)(int ,int)"
    void function(int, int);               //function has type
                                           //"void(int, int)"
    
    template<>
    struct Object1<void(int, int)>
    {
        void m_member1(int, int);  //wait, what?? not a value you can initialize.
    };
    • Diritto, che è sicuramente corretto. Anche se nel mio esempio, la dichiarazione che assume una funzione di firma come un parametro di modello non è Oggetto1. Piuttosto, la dichiarazione è filtrata attraverso una classe derivata chiamato “Oggetto”, che estrae i singoli tipi di firma.

Lascia un commento