2011-01-09 2 views
44

que je recherchais au mini-bibliothèque de Don Clugston FastDelegate et remarqué un truc syntactique bizarre avec la structure suivante:fonction des expressions semblables à des signatures que les arguments de modèle de C

TemplateClass< void(int, int) > Object; 

Il apparaît presque comme si une signature de fonction est en cours utilisé en tant qu'argument à une déclaration d'instance de modèle.

Cette technique (dont la présence dans FastDelegate est apparemment due à un Jody Hagins) a été utilisée pour simplifier la déclaration des instances de modèle avec un nombre semi-arbitraire de paramètres de modèle.

À savoir, il a permis à cette chose comme ce qui suit:

// 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; 

Malgré la véritable bouffée d'hackiness, je trouve ce genre d'émulation spoofy des arguments de modèle variadique être assez hallucinant.

Le vrai truc de cette astuce semble être le fait que je peux passer des constructions textuelles comme "Type1 (Type2, Type3)" comme arguments aux templates. Alors voici mes questions: Comment exactement le compilateur interprète-t-il cette construction? Est-ce une signature de fonction? Ou, est-ce juste un modèle de texte avec des parenthèses dedans? Si le premier, alors cela implique que toute signature de fonction arbitraire est un type valide en ce qui concerne le processeur de modèle?

Une question de suivi serait que, puisque l'exemple de code ci-dessus est un code valide, pourquoi la norme C++ ne vous permet-elle pas de faire quelque chose comme ce qui suit, qui ne compile pas?

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, excellente question et je suis un grand fan de la bibliothèque en question. –

+0

Voir le paragraphe "Types de fonctions C++ en tant que DSL" de [cet article] (http://cpp-next.com/archive/2010/11/expressive-c-fun-with-function-composition/) – icecrime

+1

Pour une autre manière pour émuler des arguments de modèles variés, voir http://templog.svn.sourceforge.net/viewvc/templog/code/trunk/tuples.h?revision=47&view=markup – sbi

Répondre

39

En ce qui concerne votre première question - sur le type int(char, float) - c'est un type valide C++ et est le type d'une fonction qui prend une char et un float et retourne un int. Notez que c'est le type de la fonction réelle, pas un pointeur de fonction, qui serait un int (*) (char, float). Le type réel de toute fonction est ce type inhabituel. Par exemple, le type de

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

est void().

La raison pour laquelle cela ne se présente pas beaucoup lors de la programmation de routine est que, dans la plupart des cas, vous ne pouvez pas déclarer des variables de ce type. Par exemple, ce code est illégal:

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

Cependant, un cas où vous ne voyez effectivement les types de fonction utilisés est pour les pointeurs de fonction qui passe autour de:

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

Il est plus fréquent de voir ce genre de fonction écrit pour prendre un pointeur de fonction, mais il est parfaitement bien d'accepter la fonction elle-même. Il est coulé dans les coulisses. En ce qui concerne votre deuxième question, pourquoi il est illégal d'avoir le même modèle écrit avec différents nombres d'arguments, je ne connais pas exactement le libellé de la spécification qui l'interdit, mais cela a quelque chose à voir avec le fait que Une fois que vous avez déclaré un modèle de classe, vous ne pouvez pas modifier le nombre d'arguments. Cependant, vous pouvez fournir une spécialisation partielle sur ce modèle qui a un nombre différent d'arguments, à condition bien sûr que la spécialisation partielle ne se spécialise que sur le nombre initial d'arguments.Par exemple:

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

Ici, Function prend toujours un paramètre. La spécialisation de modèle prend en compte deux arguments, mais la spécialisation n'est toujours que sur un type (en particulier, Ret (Arg)).

+0

Très chouette. L'idée d'un "type" d'une fonction est à peu près en dehors de l'espace conceptuel C++ au jour le jour, c'est pourquoi cette astuce m'a semblé si étrange au début. Mais si "void (int)" est un type, peut-on avoir, par exemple, un type qui est un pointeur sur "void (int)"? Serait-ce la même chose qu'un pointeur de fonction? –

+3

@Jeff - Oui, c'est un pointeur de fonction. C'est 'void (*) (int)'. –

+1

Aha, d'où la syntaxe du pointeur de fonction "correcte", par ex. "void (* fp)() = & SomeFunction;". Et aussi la fonction dereferenced appel "(* fp)();" Il me semble un peu merdique qu'il y ait des compilateurs qui vous permettent de renoncer aux opérateurs d'adresse et de déréférencement ... cela fait ressembler le type de fonction et le type de pointeur de fonction à la même chose, quand ils ne le sont apparemment pas. –

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

Oui, c'est vraiment correct. Bien que dans mon exemple, la déclaration qui prend une signature de fonction en tant que paramètre de modèle n'est pas Object1. Au lieu de cela, la déclaration est filtrée via une classe dérivée appelée "Object", qui extrait les types individuels de la signature. –

Questions connexes