web-dev-qa-db-fra.com

C++: Une macro peut-elle développer "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 <char... Chars>
struct Foo;

Je me demandais simplement s'il existait des astuces de macro qui me permettraient d'instancier cela avec une syntaxe similaire à celle-ci:

Foo<"abc">

ou

Foo<SOME_MACRO("abc")>

ou

Foo<SOME_MACRO(abc)>

etc.

Fondamentalement, tout ce qui vous empêche d’écrire les caractères individuellement, comme

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

Ce n'est pas un gros problème pour moi car c'est juste pour un programme de jouets, mais je pensais que je demanderais quand même.

38
Peter Alexander

J'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;
};

Un 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, "Hello folks") type;
  type::print();

  process(CSTR("Hi guys")());
}

Ainsi, même si vous n’obtenez pas un 'a', 'b', 'c', vous obtenez toujours des chaînes de temps de compilation.

19

Il y a eu beaucoup d'essais, mais c'est finalement voué à l'échec, je pense.

Pour comprendre pourquoi, il faut comprendre le fonctionnement du préprocesseur. L'entrée du préprocesseur peut être considérée comme un flux. Ce flux est d'abord transformé en preprocessing-tokens (liste disponible dans Le langage de programmation C++, 3e édition, Annexe A Grammar, page 795)

Sur ces jetons, le préprocesseur ne peut appliquer qu'un nombre très restreint d'opérations, en dehors des opérations digrammes/trigrammes, ce montant:

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

Et c'est tout.

  • Aucune instruction de préprocesseur ne peut diviser un jeton en plusieurs jetons: il s'agit d'une substitution de macro, ce qui signifie en fait qu'une macro est définie en premier lieu.
  • Il n'y a pas d'instruction de préprocesseur pour transformer un string-literal en un jeton régulier (en supprimant les guillemets), qui pourrait alors être soumis à une substitution de macro.

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

9
Matthieu M.

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 <STRING_TO_CHARS(3, "abc")>
//   expands to
// Foo <'a', 'b', 'c'>

De plus, à condition que le modèle en question soit capable de gérer plusieurs caractères '\ 0' de fin, nous pouvons alléger l'exigence de longueur en faveur d'une longueur maximale:

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

// Foo <STRING_TO_CHARS_ANY("abc")>
//   expands to
// Foo <'a', 'b', 'c', '\0', '\0', ...>

Les exemples ci-dessus sont compilés correctement sur clang ++ (3.2) et g ++ (4.8.0).

9
user1653543

cela fonctionnait dans une version antérieure de msvc, je ne sais pas si cela fonctionne toujours:

#define CHAR_SPLIT(...) #@__VA_ARGS__
4
P47RICK

Malheureusement, je crois que cela ne peut être fait. Le meilleur que vous pouvez obtenir du préprocesseur est fourni par Boost.Preprocessor , notamment grâce à 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 constituerait une liste d’éléments entre guillemets séparés par une virgule (voir par exemple BOOST_PP_SEQ_ENUM ), mais j’estime que l’entrée de cette macro devra être l’un des suivants: ces types, et tous exigent que les caractères soient saisis individuellement.

3
icecrime

Sur la base de ce que je discutais ci-dessus, le modèle de piratage horrible suivant peut être suffisant pour retirer ceci. Je n'ai pas testé cela (désolé!), Mais je suis à peu près sûr que cela ou quelque chose d'approchant pourrait fonctionner.

La première étape consiste à créer une classe de modèle contenant uniquement un nuage de caractères:

template <char... Chars> class CharTuple {};

Construisons maintenant un adaptateur capable de transformer une chaîne de style C en un CharTuple. Pour ce faire, nous aurons besoin de la classe d’aide suivante, qui est essentiellement un contre-style LISP pour les n-uplets:

template <typename Tuple, char ch> class Cons;
template <char... Chars, char ch> class Cons<CharTuple<Chars... ch>> {
    typedef CharTuple<ch, Chars...> type;
}

Supposons également que nous ayons une déclaration meta-if:

template <bool Condition, typename TrueType, typename FalseType> class If {
    typedef typename TrueType::type type;
};
template <typename TrueType, typename FalseType> class If<False> {
    typedef typename FalseType::type type;
};

Ensuite, ce qui suit devrait vous permettre de convertir une chaîne de style C en un tuple:

template <typename T> class Identity {
    typedef T type;
};

template <char* str> class StringToChars {
    typedef typename If<*str == '\0', Identity<CharTuple<>>,
                        Cons<*str, typename StringToChars<str + 1>::type>>::type type;
};

Maintenant que vous pouvez convertir une chaîne de style C en un tuple de caractères, vous pouvez canaliser votre chaîne d'entrée par ce type pour récupérer le tuple. Nous aurons cependant besoin d'un peu plus de machines pour que cela fonctionne. TMP n'est-il pas amusant? :-)

La première étape consiste à prendre votre code original:

template <char... Chars> class Foo { /* ... */ };

et utilisez une spécialisation de modèle pour le convertir en

template <typename> class FooImpl;
tempalte <char... Chars> class FooImpl<CharTuple<Chars...>> { /* ... */ };

C'est juste une autre couche d'indirection; rien de plus.

Enfin, vous devriez pouvoir faire ceci:

template <char* str> class Foo {
    typedef typename FooImpl<typename StringToChars<str>::type>::type type;
};

J'espère vraiment que cela fonctionne. Si ce n'est pas le cas, je pense toujours que cela vaut la peine d'être publié, car c'est probablement presque une réponse valable. :-)

2
templatetypedef

Basé sur la solution de user1653543 ci-dessus.

Un peu de magie de template:

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

template<char ... Cs>
struct split_helper;

template<char C, char ... Cs>
struct split_helper<C, Cs...>
{
    typedef Push_front_t<typename split_helper<Cs...>::type, char_<C>> type;
};

template<char ... Cs>
struct split_helper<'\0', Cs...>
{
    typedef std::integer_sequence<char> type;
};

template<char ... Cs>
using split_helper_t = typename split_helper<Cs...>::type;

Un peu de magie PP:

#define SPLIT_CHARS_EXTRACT(z, n, data) \
    BOOST_PP_COMMA_IF(n) getch(data, n)

#define STRING_N(n, str) \
    split_helper_t<BOOST_PP_REPEAT(n, SPLIT_CHARS_EXTRACT, str)>

#define STRING(str) STRING_N(BOOST_PP_LIMIT_REPEAT, str)

split_helper aide simplement à couper les zéros à la fin. Maintenant, STRING("Hello") est une séquence de caractères typée au moment de la compilation (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>). La longueur des constantes de chaîne va jusqu'à BOOST_PP_LIMIT_REPEAT caractères.

Homework : implémentez Push_front_t et c_str pour obtenir la chaîne de caractère std::integer_sequence<char, ...> terminée par un zéro. (Bien que vous puissiez essayer d'utiliser Boost.MPL)

2
Nevermore

En C++ 14, cela peut être fait en utilisant un lambda immédiatement appelé et une fonction membre static, similaire à BOOST_HANA_STRING :

#include <utility>

template <char... Cs>
struct my_string {};

template <typename T, std::size_t... Is>
constexpr auto as_chars_impl(std::index_sequence<Is...>) {
    return my_string<T::str()[Is]...>{};
}

template <typename T>
constexpr auto as_chars() {
    return as_chars_impl<T>(
        std::make_index_sequence<sizeof(T::str())-1>{});
}

#define STR(literal)                                        \
    []{                                                     \
        struct literal_to_chars {                           \
            static constexpr decltype(auto) str() {         \
                return literal;                             \
            }                                               \
        };                                                  \
        return as_chars<literal_to_chars>();                \
    }()

Live on Godbolt

Avant C++ 17, l'objet renvoyé par STR("some literal") ne peut pas être constexpr car lambda ne peut pas être constexpr. Avant C++ 20, vous ne pouvez pas simplement écrire decltype(STR("some literal")) car les lambdas ne sont pas autorisés dans des contextes non évalués.

0
Justin