2011-01-03 18 views
38

Ho scritto un modello variadic che accetta un numero variabile di char parametri, vale a direC++: una macro può espandere "abc" in "a", "b", "c"?

template <char... Chars> 
struct Foo; 

Mi stavo chiedendo se ci fossero trucchi macro che mi permettesse di creare un'istanza di questo con una sintassi simile al seguente:

Foo<"abc"> 

o

Foo<SOME_MACRO("abc")> 

o

Foo<SOME_MACRO(abc)> 

ecc

Fondamentalmente, tutto ciò che ti impedisce di dover scrivere i caratteri singolarmente, in questo modo

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

Questo non è un grosso problema per me in quanto è solo per un giocattolo programma, ma ho pensato di chiedere comunque.

+1

'" abc "' è essenzialmente lo stesso di '' a ',' b ',' c ',' \ 0'', ad eccezione del puntatore. –

+0

Solitamente non è stato possibile creare un'istanza di un modello in C++ utilizzando una stringa C non elaborata se il modello è stato parametrizzato su un char *. Lo hanno risolto in C++ 0x? Se è così, penso di avere un modo di fare questa espansione correttamente. – templatetypedef

+0

@Ignacio: Lo so, ma non puoi scrivere '" abc "' per un argomento template 'char ...'. @templatetypedef: Il modello non è parametrizzato su 'char *', è un modello variadico su 'char ...' –

risposta

4

questo usato per lavorare in una prima versione di msvc, non so se lo fa ancora:

#define CHAR_SPLIT(...) #@__VA_ARGS__ 
+0

Non ho MSVC su di me, ma non funziona in GCC :-( –

+0

scusate, quindi sono fuori idee :( – P47RICK

2

In base a quello che stavo discutendo sopra, il seguente modello hackery terribile può essere sufficiente per tirare questo fuori. Non ho provato questo (scusate!), Ma sono abbastanza sicuro che o qualcosa di simile potrebbe funzionare.

Il primo passo è quello di costruire una classe template che solo detiene una tupla di caratteri:

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

Ora, costruiamo un adattatore in grado di trasformare una stringa in stile C in un CharTuple. Per fare questo, avremo bisogno la seguente classe di supporto che è essenzialmente un contro LISP-stile per tuple:

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

Supponiamo anche noi abbiamo una 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; 
}; 

Poi il segue dovrebbe permetterà di convertire una stringa in stile C in 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; 
}; 

Ora che è possibile convertire una stringa in stile C in una tupla di caratteri, si può incanalare la stringa di input attraverso questo tipo di recuperare il tupla . Avremo bisogno di fare un po 'più di macchinari per farlo funzionare, però. Non è divertente il TMP? :-)

Il primo passo è quello di prendere il vostro codice originale:

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

e utilizzare alcuni modello di specializzazione per convertirlo in

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

E 'solo un altro livello di riferimento indiretto; niente di più.

Infine, si dovrebbe essere in grado di fare questo:

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

Spero davvero che questo funziona. In caso contrario, ritengo comunque che valga la pena pubblicare questo messaggio perché probabilmente è & epsilon; -giungi a una risposta valida. :-)

+0

I don ' Credo che si possa dereferenziare 'str' in fase di compilazione, ma ci provo io. –

+0

Questo è un punto valido, non ho mai provato a fare qualcosa del genere prima. Potresti potenzialmente avere una specializzazione di template del tipo la stringa vuota? Oppure non funzionerebbe correttamente? – templatetypedef

+0

Sì, avevo ragione, non puoi dereferenziare in fase di compilazione. –

3

Sfortunatamente, credo che questo non possa essere fatto. Il meglio che si può ottenere dal preprocessore è fornito da Boost.Preprocessor, in particolare attraverso i suoi tipi di dati:

  • array: sintassi sarebbe (3, (a, b, c))
  • list: sintassi sarebbe (a, (b, (c, BOOST_PP_NIL)))
  • sequence: sintassi sarebbe (a)(b)(c)
  • tuple: sintassi sarebbe (a, b, c)

Da uno di questi tipi, è possibile creare facilmente una macro che creerebbe un elenco separato da virgole di elementi racchiusi in virgolette (vedere ad esempio BOOST_PP_SEQ_ENUM), ma credo che l'input di questa macro dovrà essere uno di questi tipi e tutti richiedono che i caratteri vengano digitati singolarmente.

9

C'è stato un sacco di prove, ma alla fine è destinato a fallire credo.

Per capire perché, è necessario capire come funziona il preprocessore. L'input del preprocessore può essere pensato come un flusso. Questo flusso viene prima trasformato in di pre-elaborazione-gettoni (lista availabe in The C++ Programming Language, 3rd Edition, Allegato A Grammar, pagina 795)

Su questi token, il preprocessore può applicare solo un numero molto ristretto di operazioni, a parte i digrammi/trigrammi roba, questo importo a:

  • inclusione di file (per le direttive di intestazione), questo non può essere visualizzato in una macro per quanto ne so
  • sostituzione macro (che è roba estremamente complicato: p)
  • #: trasforma un token in una stringa di letterali Token (circondandolo tra virgolette)
  • ##: concatena due token

E questo è tutto.

  • v'è alcuna istruzione preprocessore che può dividere un gettone nella vari token: questa è la sostituzione di macro, il che significa in realtà avere una macro definita in primo luogo
  • v'è alcuna istruzione preprocessore trasformare uno String letterale in un token regolare (rimuovendo le virgolette) che potrebbe quindi essere soggetto alla sostituzione delle macro.

Pertanto ritengo che sia impossibile (sia in C++ 03 o C++ 0x), anche se potrebbero esserci (eventualmente) estensioni specifiche del compilatore per questo.

+0

Questo è sfortunato, ma penso che tu abbia ragione. Qualcosa di più potente di un preprocessore C++ è richiesto per tale operazione. –

+0

Il '#' è utile qui. Permetterebbe a 'FOO (a, b, c)' di essere espanso in ''a', 'b', 'c''. Usate queste due macro: '#finefile asChar (x) #x [0]' e '#define FOO (x, y, z) asChar (asChar (x)), asChar (asChar (y)), asChar (asChar (z)) 'Testato e funziona. Peccato che questa semplice versione sia codificata a tre caratteri. clang3.3 e g ++ - 4.6, ma non penso che stia usando qualcosa di troppo elaborato. Se '" sdlkfj "[0]' valuta '' s'' in fase di compilazione, allora dovrebbe funzionare su qualsiasi compilatore. –

19

Ne ho creato uno oggi e testato su 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; 
}; 

Alcuni test di

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")()); 
} 

Così, mentre non si ottiene un 'a', 'b', 'c', è ancora ottenere la compilazione stringhe di tempo.

+0

Interessante, ma ho ragione nel dire che gestisce solo fino a 18 stringhe? –

+1

@peter, sì. ma puoi aggiungere solo più E'es. quindi non una vera limitazione. c.f. boost.pp –

+0

@litb: Vero, ma le stringhe nel mio caso d'uso potrebbero facilmente andare in migliaia di caratteri :-) –

9

Una soluzione basata sulla risposta di Sylvain Defresne di cui sopra è possibile in 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'> 

Inoltre, a condizione che il modello in questione è in grado di gestire terminazione più caratteri '\ 0', possiamo facilitare il requisito di lunghezza in favore di una lunghezza massima:

#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', ...> 

Gli esempi sopra compilare correttamente clang ++ (3.2) e g ++ (4.8.0).

1

Basato su user1653543 soluzione sopra.

Alcuni template magia:

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; 

qualche magia 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 solo aiuto per tagliare zeri finali. Ora STRING("Hello") è una sequenza di caratteri in fase di compilazione scritta (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>). La lunghezza delle costanti di stringa è pari a BOOST_PP_LIMIT_REPEAT caratteri.

Compito: attuare push_front_t e c_str ottenere la stringa di terminazione null di std::integer_sequence<char, ...>. (Anche se puoi provare a usare Boost.MPL)

Problemi correlati