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.
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.
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:
#
: transforme un jeton en string-literal token (en l'entourant de guillemets)##
: concatène deux jetonsEt c'est tout.
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.
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).
cela fonctionnait dans une version antérieure de msvc, je ne sais pas si cela fonctionne toujours:
#define CHAR_SPLIT(...) #@__VA_ARGS__
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.
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. :-)
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)
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>(); \
}()
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.