40 votes

C++ : Est-ce qu'une macro peut étendre "abc" en 'a', 'b', 'c'?

J'ai écrit un modèle variadique qui accepte un nombre variable de paramètres char, c'est-à-dire :

template 
struct Foo;

Je me demandais simplement s'il existait des astuces de macro qui me permettraient d'instancier ceci avec une syntaxe similaire à ce qui suit :

Foo<"abc">

ou

Foo

ou

Foo

etc.

En gros, tout ce qui vous empêche d'avoir à écrire les caractères individuellement, comme ceci :

Foo<'a', 'b', 'c'>

Ce n'est pas un gros problème pour moi car c'est juste pour un programme jouet, mais j'ai quand même pensé demander.

1 votes

"abc" est essentiellement la même chose que 'a', 'b', 'c', '\0', sauf pour les pointeurs.

0 votes

Il était autrefois impossible d'instancier un modèle en C++ en utilisant une chaîne C brute si le modèle était paramétré sur un char*. Est-ce que cela a été corrigé dans C++0x ? Si c'est le cas, je pense avoir une façon de faire cette expansion correctement.

0 votes

@Ignacio: Je sais ça, mais tu ne peux pas écrire "abc" pour un argument de modèle char.... @templatetypedef: Le modèle n'est pas paramétré sur char*, c'est un modèle variadique sur char...

21voto

J'en ai créé un aujourd'hui, et testé sur GCC4.6.0.

#include <iostream>

#define E(L,I) \
  (I < sizeof(L)) ? L[I] : 0

#define STR(X, L)                                                       \
  typename Expand<X,                                                    \
                  cstring<E(L,0),E(L,1),E(L,2),E(L,3),E(L,4), E(L,5),   \
                          E(L,6),E(L,7),E(L,8),E(L,9),E(L,10), E(L,11), \
                          E(L,12),E(L,13),E(L,14),E(L,15),E(L,16), E(L,17)> \
                  cstring<>, sizeof L-1>::type

#define CSTR(L) STR(cstring, L)

template<char ...C> struct cstring { };

template<template<char...> class P, typename S, typename R, int N>
struct Expand;

template<template<char...> class P, char S1, char ...S, char ...R, int N>
struct Expand<P, cstring<S1, S...>, cstring<R...>, N> :
  Expand<P, cstring<S...>, cstring<R..., S1>, N-1>{ };

template<template<char...> class P, char S1, char ...S, char ...R>
struct Expand<P, cstring<S1, S...>, cstring<R...>, 0> {
  typedef P<R...> type;
};

Certain test

template<char ...S> 
struct Test {
  static void print() {
    char x[] = { S... };
    std::cout << sizeof...(S) << std::endl;
    std::cout << x << std::endl;
  }
};

template<char ...C>
void process(cstring<C...>) {
  /* process C, possibly at compile time */
}

int main() {
  typedef STR(Test, "Bonjour tout le monde") type;
  type::print();

  process(CSTR("Salut les amis")());
}

Ainsi, même si vous n'obtenez pas un 'a', 'b', 'c', vous obtenez toujours des chaînes de caractères au moment de la compilation.

0 votes

Intéressant, mais ai-je raison de dire que cela ne gère que les chaînes de longueur 18 ?

1 votes

@peter, oui. mais vous pouvez simplement ajouter plus de E. donc pas une vraie limitation. c.f. boost.pp

0 votes

@litb: Vrai, mais les chaînes dans mon cas d'utilisation pourraient facilement atteindre des milliers de caractères :-)

10voto

user1653543 Points 71

Une solution basée sur la réponse de Sylvain Defresne ci-dessus est possible en C++11:

#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>

template <unsigned int N>
constexpr char get_ch (char const (&s) [N], unsigned int i)
{
    return i >= N ? '\0' : s[i];
}

#define STRING_TO_CHARS_EXTRACT(z, n, data) \
        BOOST_PP_COMMA_IF(n) get_ch(data, n)

#define STRING_TO_CHARS(STRLEN, STR)  \
        BOOST_PP_REPEAT(STRLEN, STRING_TO_CHARS_EXTRACT, STR)

// Foo 
//   expands to
// Foo <'a', 'b', 'c'>

De plus, si le modèle en question est capable de traiter plusieurs caractères terminaux '\0', nous pouvons assouplir l'exigence de longueur en faveur d'une longueur maximale:

#define STRING_TO_CHARS_ANY(STR) \
        STRING_TO_CHARS(100, STR)

// Foo 
//   expands to
// Foo <'a', 'b', 'c', '\0', '\0', ...>

Les exemples ci-dessus se compilent correctement sur clang++ (3.2) et g++ (4.8.0).

9voto

Matthieu M. Points 101624

Il y a eu beaucoup de tentatives, mais je pense que c'est finalement voué à l'échec.

Pour comprendre pourquoi, il faut comprendre comment fonctionne le préprocesseur. L'entrée du préprocesseur peut être considérée comme un flux. Ce flux est d'abord transformé en jetons de prétraitement (liste disponible dans Le langage de programmation C++, 3ème édition, Annexe A Grammaire, page 795)

Sur ces jetons, le préprocesseur peut appliquer un nombre très restreint d'opérations, en dehors de trucs de digrammes/trigrammes, cela revient à :

  • inclusion de fichier (pour les directives d'en-tête), cela ne peut pas apparaître dans une macro autant que je sache
  • substitution de macro (qui est extrêmement compliqué :p)
  • # : transforme un jeton en un jeton de chaîne de caractères (en le encadrant de guillemets)
  • ## : concatène deux jetons

Et c'est tout.

  • Il n'y a pas d'instruction de préprocesseur qui peut diviser un jeton en plusieurs jetons : c'est la substitution de macro, ce qui signifie avoir en fait une macro définie en premier lieu
  • Il n'y a pas d'instruction de préprocesseur pour transformer un jeton de chaîne de caractères en un jeton régulier (en enlevant les guillemets) qui pourrait ensuite être soumis à une substitution de macro.

Je maintiens donc que c'est impossible (que ce soit en C++03 ou C++0x), bien qu'il puisse (éventuellement) y avoir des extensions spécifiques au compilateur pour cela.

0 votes

C'est malheureux, mais je pense que tu as raison. Quelque chose de plus puissant qu'un préprocesseur C++ est nécessaire pour une telle opération.

0 votes

Le # est utile ici. Cela permettrait à FOO(a,b,c) d'être développé en 'a','b','c'. Utilisez ces deux macros : #define asChar(x) #x[0] et #define FOO(x,y,z) asChar(asChar(x)),asChar(asChar(y)),asChar(asChar(z)). Testé et ça fonctionne. C'est dommage que cette version simple soit codée en dur pour trois caractères. clang3.3 et g++-4.6 sont utilisés, mais je ne pense pas que cela utilise quelque chose de trop compliqué. Si "sdlkfj"[0] évalue à 's' à la compilation, alors cela devrait fonctionner sur n'importe quel compilateur.

4voto

P47RICK Points 116

Cela fonctionnait dans une version antérieure de msvc, je ne sais pas si ça marche encore :

#define CHAR_SPLIT(...) #@__VA_ARGS__

0 votes

Désolé, alors je suis à court d'idées :(

3voto

icecrime Points 23650

Malheureusement, je crois que cela ne peut pas être fait. Le meilleur que vous puissiez obtenir du préprocesseur est fourni par Boost.Preprocessor, notamment à travers ses types de données :

  • array : la syntaxe serait (3, (a, b, c))
  • list : la syntaxe serait (a, (b, (c, BOOST_PP_NIL)))
  • sequence : la syntaxe serait (a)(b)(c)
  • tuple : la syntaxe serait (a, b, c)

À partir de l'un de ces types, vous pouvez facilement créer une macro qui construirait une liste séparée par des virgules d'éléments entre guillemets simples (voir par exemple BOOST_PP_SEQ_ENUM), mais je crois que l'entrée de cette macro devra être l'un de ces types, et tous nécessitent que les caractères soient tapés individuellement.

1 votes

Je me suis arrêté sur le BOOST_PP_SEQ_ENUM mais comment puis-je ajouter les guillemets ?

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