25 votes

Problèmes avec les paramètres de modèles utilisés dans les macros

J'essaye de compiler le code suivant, j'obtiens une erreur sur la ligne qui spécialise std::vector, il semble que le seul paramètre passé soit en quelque sorte considéré comme deux paramètres. Est-ce que cela a quelque chose à voir avec les crochets ?

Existe-t-il un moyen/mécanisme spécial permettant de transmettre correctement ces paramètres à la macro ?

#include <vector>

template<typename A>
struct AClass {};

#define specialize_AClass(X)\
template<> struct AClass<X> { X a; };

specialize_AClass(int) //ok

specialize_AClass(std::vector<int,std::allocator<int> >) //error

int main()
{
   return 0;
}

L'erreur que je reçois est la suivante :

1 Line 55: error: macro "specialize_AClass" passed 2 arguments, but takes just 1
2 Line 15: error: expected constructor, destructor, or type conversion before 'int'
3 compilation terminated due to -Wfatal-errors.

Lien : http://codepad.org/qIiKsw4l

13voto

calmman Points 121
template<typename TypeX, typename TypeY>
class Test
{
public:
    void fun(TypeX x, TypeY y)
    {
        std::wcout << _T("Hello") << std::endl;
        std::wcout << x << std::endl;
        std::wcout << y << std::endl;
    }
};

#define COMMOA ,

#define KK(x) x val;

void main()
{
    KK(Test<int COMMOA int>);
    val.fun(12, 13);
}

J'ai une nouvelle méthode pour résoudre ce problème. J'espère qu'elle pourra vous aider :)

12voto

Vous avez deux options. L'une d'entre elles a déjà été mentionnée : Utiliser __VA_ARGS__ . Cette méthode présente toutefois l'inconvénient de ne pas fonctionner en C++03 strict, mais nécessite un préprocesseur suffisamment compatible C99/C++0x.

L'autre option consiste à mettre le nom du type entre parenthèses. Mais contrairement à ce que prétend une autre réponse, ce n'est pas aussi simple que de mettre le nom du type entre parenthèses. Écrire une spécialisation comme suit est mal formé

// error, NOT valid!
template<> struct AClass<(int)> { X a; };

J'ai contourné ce problème (et Boost utilise probablement la même chose) en passant le nom du type entre parenthèses, puis en construisant un type de fonction à partir de ce nom.

template<typename T> struct get_first_param;
template<typename R, typename P1> struct get_first_param<R(P1)> {
  typedef P1 type;
};

Avec ça, get_first_param<void(X)>::type désigne le type X . Vous pouvez maintenant réécrire votre macro pour

#define specialize_AClass(X) \
template<> struct AClass<get_first_param<void X>::type> { 
  get_first_param<void X>::type a; 
};

Et il suffit de passer le type enveloppé entre parenthèses.

6voto

Matthieu M. Points 101624

Il y a deux problèmes ici.

Tout d'abord, les macros sont extrêmement stupides, elles sont compliquées, mais se résument essentiellement à un processus de remplacement de texte pur.

Il y a donc 2 problèmes (techniques) avec le code que vous avez exposé :

  1. Vous ne pouvez pas utiliser de virgule au milieu d'une invocation de macro, cela échoue, BOOST_FOREACH est une bibliothèque bien connue et pourtant la seule chose qu'ils ont pu faire a été de dire à l'utilisateur que ses arguments ne devaient pas contenir de virgules, à moins qu'ils ne puissent être mis entre parenthèses, ce qui n'est pas toujours le cas
  2. Même si le remplacement se produisait, votre code échouerait en C++03, parce qu'il créerait un fichier >> à la fin de la spécialisation du modèle, qui ne serait pas analysé correctement.

Il existe des astuces de prétraitement / métaprogrammation des modèles, mais la solution la plus simple consiste à utiliser un type sans virgule :

typedef std::vector<int, std::allocator<int> > FooVector;
specialize_AClass(FooVector)

Enfin, il y a une question d'esthétique : en raison de leur omniprésence, il est préférable de donner aux macros des noms qui ne peuvent pas entrer en conflit avec les noms "ordinaires" (types, fonctions, variables). Le consensus est généralement d'utiliser tous les identifiants en majuscules, comme dans :

SPECIALIZE_ACLASS

Notez que cela ne peut pas commencer par un trait de soulignement, car la norme restreint l'utilisation d'identifiants correspondant à _[A-Z].* o [^_]*__.* aux auteurs de compilateurs pour la bibliothèque standard ou ce qu'ils veulent (ce ne sont pas des smileys :p)

2voto

Justin Spahr-Summers Points 12167

Comme le préprocesseur s'exécute avant l'analyse sémantique, la virgule dans le paramètre de votre modèle est interprétée comme le séparateur d'argument de la macro. Au lieu de cela, vous devriez être en mesure d'utiliser des macros variadiques pour faire quelque chose comme ceci :

#define specialize_AClass(...)\
template<> struct AClass< __VA_ARGS__ > { X a; };

1voto

Eric Zinda Points 102

Si vous êtes prêt à ajouter un peu plus de code avant d'appeler votre macro, vous pouvez toujours le faire comme solution de rechange :

typedef std::vector<int,std::allocator<int> > myTypeDef; 
specialize_AClass(myTypeDef) //works

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X