2011-01-09 25 views
44

stavo guardando di don Clugston FastDelegate mini-biblioteca e ho notato un trucco sintattica strano con la seguente struttura:funzione di firma, come espressioni come C++ argomenti template

TemplateClass< void(int, int) > Object; 

Sembra quasi come se una firma di funzione è in corso utilizzato come argomento per una dichiarazione di istanza del modello.

Questa tecnica (la cui presenza in FastDelegate è apparentemente dovuta a un Jody Hagins) è stata utilizzata per semplificare la dichiarazione delle istanze del modello con un numero semi-arbitrario di parametri del modello.

Vale a dire, ha permesso 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 questo tipo di emulazione Spoofy di argomenti template variadic di essere abbastanza sconvolgente.

La vera carne di questo trucco sembra essere il fatto che posso passare costrutti testuali come "Tipo1 (Tipo2, Tipo3)" come argomenti ai modelli. Quindi ecco le mie domande: in che modo esattamente il compilatore interpreta questo costrutto? È una firma di funzione? Oppure, è solo un modello di testo con parentesi in esso? Se il primo, allora implica che qualsiasi firma di funzione arbitraria è un tipo valido per quanto riguarda il processore di template?

Una domanda successiva è che, poiché il codice sopra riportato è un codice valido, perché lo standard C++ non consente solo di fare qualcosa di simile al seguente, che non viene compilato?

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; 
+1

+1, ottima domanda e sono un grande fan della biblioteca in questione. –

+0

Vedere il paragrafo "Tipi di funzione C++ come DSL" di [questo articolo] (http://cpp-next.com/archive/2010/11/expressive-c-fun-with-function-composition/) – icecrime

+1

Per un altro modo per emulare gli argomenti dei modelli variadic vedere http://templog.svn.sourceforge.net/viewvc/templog/code/trunk/tuples.h?revision=47&view=markup – sbi

risposta

39

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

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

è void().

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

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

Tuttavia, un caso in cui non effettivamente vedere i tipi di funzione utilizzata è per il passaggio puntatori a funzione in giro:

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

E 'più comune vedere questo tipo di funzione scritto per assumere un puntatore a funzione, ma è perfettamente accettabile prendere la funzione stessa. Viene castato dietro le quinte.

Per quanto riguarda la seconda domanda, perché è illegale avere lo stesso modello scritto con diversi numeri di argomenti, non conosco il testo esatto nelle specifiche che lo proibiscono, ma ha qualcosa a che fare con il fatto che una volta dichiarato un modello di classe, non è possibile modificare il numero di argomenti. Tuttavia, è possibile fornire una specializzazione parziale su quel modello che ha un diverso numero di argomenti, purché ovviamente la specializzazione parziale si specializzi solo sul numero originale di argomenti.Per esempio:

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

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

+0

Molto elegante. L'idea di un "tipo" di una funzione è praticamente al di fuori dello spazio concetto C++ di tutti i giorni, ed è per questo che all'inizio questo trucco mi è sembrato così strano. Ma così se "void (int)" è un tipo, si può avere, ad esempio, un tipo che è un puntatore a "void (int)"? Sarebbe la stessa cosa di un puntatore a funzione? –

+3

@Jeff - Sì, è un puntatore a funzione. È 'void (*) (int)'. –

+1

Aha, quindi la sintassi del puntatore della funzione "corretta", ad es. "void (* fp)() = & SomeFunction;". E anche la funzione chiamata dereferenziata "(* fp)();" Mi sembra un po 'schifoso che ci siano dei compilatori che ti permettono di rinunciare agli operatori di indirizzamento e dereferenzialità ... rende il tipo di funzione e il tipo di puntatore a funzione sembrano la stessa cosa, quando apparentemente non lo sono. –

3
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. 
}; 
+0

Giusto, questo è sicuramente corretto. Sebbene nel mio esempio, la dichiarazione che accetta una firma di funzione come parametro del modello non è Object1. Piuttosto, la dichiarazione viene filtrata attraverso una classe derivata chiamata "Oggetto", che estrae i singoli tipi dalla firma. –

Problemi correlati