2011-01-03 13 views
38

Pisałem o zmiennej liczbie argumentów szablonu, który akceptuje zmienną liczbę char parametrów, tjC++: Czy makro może rozwinąć "abc" na "a", "b", "c"?

template <char... Chars> 
struct Foo; 

Zastanawiam się, czy były jakieś makro sztuczki, które pozwoliłoby mnie do wystąpienia tego ze składni podobnej do następującej:

Foo<"abc"> 

lub

Foo<SOME_MACRO("abc")> 

lub

Foo<SOME_MACRO(abc)> 

itp

W zasadzie wszystko, co zatrzymuje cię od konieczności pisania znaków indywidualnie, tak jak

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

Nie jest to duży problem dla mnie, to jest po prostu za zabawkę program, ale pomyślałem, że i tak zapytam.

+1

'" abc "' jest zasadniczo takie samo jak ''a', 'b', 'c', '\ 0'', z wyjątkiem wskaźników. –

+0

Kiedyś nie można było utworzyć instancji szablonu w C++ przy użyciu surowego łańcucha C, jeśli szablon został sparametryzowany za pomocą znaku *. Czy naprawili to w C++ 0x? Jeśli tak, myślę, że mam sposób na poprawne wykonanie tego rozszerzenia. – templatetypedef

+0

@Ignacio: Wiem o tym, ale nie możesz napisać '" abc "' dla argumentu "char ..." szablonu. @templatetypedef: Szablon nie jest parametryzowany przez 'char *', jest szablonem variadic nad 'char ...' –

Odpowiedz

4

to używane do pracy we wczesnej wersji MSVC, ja nie wiem, czy to nadal robi:

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

Nie mam MSVC na mnie, ale to nie działa w GCC :-( –

+0

przepraszam, a potem mam nieudane pomysły :( – P47RICK

2

Na podstawie tego, co zostało wyżej omawiając następujące straszny szablon hackery może być wystarczające, aby wyciągnąć to wyłączone. Nie testowałem tego (przepraszam!), Ale jestem prawie pewien, że może to działać lub coś podobnego.

Pierwszym krokiem jest stworzenie klasy szablon, który po prostu trzyma krotka znaków:

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

Teraz zbudujmy adapter, który można przekształcić ciąg C-styl w CharTuple. Aby to zrobić, musimy następujące klasy pomocnika, który jest zasadniczo minusy LISP stylu dla krotek:

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

Niech również zakładać, że mamy 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; 
}; 

Wtedy następujące powinny pozwolić ci przekonwertować ciąg C-styl w krotce:

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

teraz można przekonwertować ciąg C-styl w krotką znaków, można lejek swój ciąg wejściowy za pośrednictwem tego typu odzyskanie krotki . Będziemy musieli zrobić trochę więcej maszyn, aby to zadziałało. Czy TMP nie jest zabawne? :-)

Pierwszym krokiem jest, aby wziąć swój oryginalny kod:

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

i korzystać z niektórych specjalizacji szablonu, aby przekształcić go

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

To kolejna warstwa zadnie; nic więcej.

Wreszcie, powinieneś być w stanie to zrobić:

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

Mam nadzieję, że to działa. Jeśli nie, nadal uważam, że warto go publikować, ponieważ jest to prawdopodobnie & epsilon; -zamknij, aby uzyskać prawidłową odpowiedź. :-)

+0

I don ' t wierzę, że możesz wyłuskać 'str' w czasie kompilacji, ale dam mu szansę –

+0

To jest poprawny punkt, nigdy naprawdę nie próbowałem robić czegoś takiego wcześniej. Czy mógłbyś potencjalnie mieć specjalizację szablonu typu ponad pusty ciąg znaków? Czy też nie działałoby to poprawnie? – templatetypedef

+0

Tak, miałem rację, nie można usunąć dereferencji podczas kompilacji –

3

Niestety, uważam, że nie można tego zrobić. Najlepiej można dostać się z preprocesora jest przez Boost.Preprocessor, przede wszystkim za pośrednictwem swoich typów danych:

  • array: składnia byłoby (3, (a, b, c))
  • list: składnia byłoby (a, (b, (c, BOOST_PP_NIL)))
  • sequence: składnia byłoby (a)(b)(c)
  • tuple: Składnia będzie: (a, b, c)

Z dowolnego z tych typów można łatwo utworzyć makro, które tworzyłoby oddzieloną przecinkami listę pojedynczych cytowanych załączonych elementów (patrz na przykład BOOST_PP_SEQ_ENUM), ale uważam, że dane wejściowe tego makra będą musiały być jednym z tych typów , a wszystkie wymagają osobnego wpisywania znaków.

9

Było wiele prób, ale ostatecznie skazany jestem na porażkę.

Aby zrozumieć, dlaczego, należy zrozumieć, jak działa preprocesor. Wejście preprocesora można traktować jako strumień. Ten strumień jest najpierw przekształcona w preprocesora-tokenów (lista dostępnej w C++ Programming Language, 3rd Edition, Aneks Gramatyka, strona 795)

Na tych tokenów, preprocesor może ubiegać się jedynie bardzo ograniczoną liczbę operacji, oprócz digrams/trygramów rzeczy, tę kwotę:

  • włączenia pliku (na dyrektywach nagłówku), to nie może się pojawić w makro o ile wiem
  • makro podstawienie (co jest niezwykle skomplikowane rzeczy: p)
  • #: zamienia token do string-dosłownym tokena (przez otaczający ją cytaty)
  • ##: łączy dwa znaki

I to wszystko.

  • Brak instrukcji preprocesora, które mogą podzielić wyraz na kilka tokenów: to makr, co oznacza faktycznie mający makra zdefiniowane w pierwszej kolejności
  • Brak instrukcji preprocesora przekształcić String literał do zwykłego tokena (usuwanie cudzysłowów), który może następnie podlegać podstawianiu makr.

W związku z tym twierdzę, że jest niemożliwe (w C++ 03 lub C++ 0x), choć mogą być (być może) rozszerzenia dla kompilatora dla tego.

+0

To niefortunne, ale myślę, że masz rację. Coś potężniejszego niż preprocesor C++ jest wymagane do takiej operacji. –

+0

"#" jest tutaj użyteczne. Pozwoliłoby to rozszerzyć 'FOO (a, b, c)' na ''a', 'b', 'c''. Użyj tych dwóch makr: '#define asChar (x) #x [0]' i '#define FOO (x, y, z) asChar (asChar (x)), asChar (asChar (y)), asChar (asChar (z)) 'Testowany i działa. Szkoda, że ​​ta prosta wersja jest zakodowana na trzy znaki. clang3.3 i g ++ - 4.6, ale nie sądzę, że używa czegoś zbyt fantazyjnego. Jeśli '' sdlkfj "[0]' zwraca wartość ''s'' w czasie kompilacji, powinno działać na dowolnym kompilatorze. –

19

Stworzyłem go dzisiaj i przetestowałem na 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; 
}; 

Niektóre testy

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

Więc choć nie dostać 'a', 'b', 'c', nadal się skompilować ciągów czasowych.

+0

Interesujące, ale czy mam rację mówiąc, że obsługuje tylko do 18 ciągów? –

+1

@peter, tak. ale możesz po prostu dodać więcej E'es. więc nie jest prawdziwym ograniczeniem. c.f. boost.pp –

+0

@litb: To prawda, ale ciągi w moim przypadku użycia mogą z łatwością przejść do tysięcy znaków :-) –

9

rozwiązanie oparte na reakcji Sylvain Defresne za powyższe jest możliwe w 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'> 

Ponadto, pod warunkiem, że szablon, o którym mowa jest w stanie obsługiwać wiele kończącej znaków „\ 0”, możemy złagodzić Wymagana długość na rzecz maksymalnej długości:

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

Powyższe przykłady poprawnie się kompilują w języku ++ (3.2) i g ++ (4.8.0).

1

Na podstawie powyższego rozwiązania użytkownika: użytkownik1653543.

Niektóre szablon 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; 

Niektóre PP magia:

#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 tylko pomocnikiem wyciąć końcowe zera. Teraz STRING("Hello") jest typową sekwencją znaków w czasie kompilacji (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>). Długość stałych łańcuchowych wynosi do BOOST_PP_LIMIT_REPEAT znaków.

zadania: wdrożenie push_front_t i c_str dostać NUL ciąg std::integer_sequence<char, ...>. (Mimo, że możesz spróbować użyć Boost.MPL)

Powiązane problemy