webentwicklung-frage-antwort-db.com.de

C++: Kann ein Makro "abc" in "a", "b", "c" erweitern?

Ich habe eine Variadic-Vorlage geschrieben, die eine variable Anzahl von char-Parametern akzeptiert, d. H.

template <char... Chars>
struct Foo;

Ich habe mich nur gefragt, ob es Makrotricks gibt, mit denen ich dies mit einer Syntax wie der folgenden instanziieren kann:

Foo<"abc">

oder

Foo<SOME_MACRO("abc")>

oder

Foo<SOME_MACRO(abc)>

usw.

Grundsätzlich alles, was Sie davon abhält, die Zeichen einzeln zu schreiben

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

Dies ist kein großes Problem für mich, da es nur für ein Spielzeugprogramm ist, aber ich dachte, ich würde trotzdem fragen.

38
Peter Alexander

Ich habe heute eine erstellt und auf GCC4.6.0 getestet.

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

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

Wenn Sie also keinen 'a', 'b', 'c' erhalten, erhalten Sie trotzdem Kompilierzeittabellen.

Es hat viele Versuche gegeben, aber ich denke, es ist letztendlich zum Scheitern verurteilt.

Um zu verstehen, warum, muss man verstehen, wie der Präprozessor funktioniert. Die Eingabe des Präprozessors kann als Stream betrachtet werden. Dieser Stream wird zuerst in Preprocessing-Token transformiert (Liste verfügbar in The C++ Programming Language, 3. Auflage, Anhang A Grammatik, Seite 795)

Auf diese Token kann der Präprozessor nur eine sehr begrenzte Anzahl von Operationen anwenden, abgesehen von den Digrammen/Trigrammen, die sich auf Folgendes belaufen:

  • datei-Aufnahme (für Header-Direktiven), erscheint dies meines Wissens möglicherweise nicht in einem Makro
  • makrosubstitution (das ist extrem kompliziertes Zeug: p)
  • #: wandelt ein Token in ein String-Literal Token um (indem es in Anführungszeichen gesetzt wird)
  • ##: Verkettet zwei Token

Und das ist es.

  • Es gibt keinen Präprozessor-Befehl, der ein Token in mehrere Token aufteilen könnte: Dies ist eine Makrosubstitution, dh, ein Makro ist tatsächlich an erster Stelle definiert
  • Es gibt keinen Präprozessor-Befehl, um ein String-Literal in ein reguläres Token umzuwandeln (indem die Anführungszeichen entfernt werden), das dann durch ein Makro ersetzt werden könnte.

Ich behaupte daher, dass es unmöglich ist (entweder in C++ 03 oder C++ 0x), obwohl es (möglicherweise) compilerspezifische Erweiterungen dafür geben könnte.

9
Matthieu M.

Eine Lösung, die auf der obigen Antwort von Sylvain Defresne basiert, ist in C++ 11 möglich:

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

Vorausgesetzt, die betreffende Vorlage kann mehrere abschließende '\ 0'-Zeichen verarbeiten, kann die Längenanforderung zugunsten einer maximalen Länge vereinfacht werden:

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

Die obigen Beispiele werden unter clang ++ (3.2) und g ++ (4.8.0) korrekt kompiliert.

9
user1653543

früher funktionierte dies in einer frühen Version von msvc. Ich weiß nicht, ob es immer noch funktioniert:

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

Leider glaube ich, dass dies nicht möglich ist. Das Beste, was Sie vom Präprozessor erhalten können, wird von Boost.Preprocessor bereitgestellt, insbesondere durch seine Datentypen:

  • array : Syntax wäre (3, (a, b, c))
  • list : Syntax wäre (a, (b, (c, BOOST_PP_NIL)))
  • sequence : Syntax wäre (a)(b)(c)
  • Tuple : Syntax wäre (a, b, c)

Aus jedem dieser Typen können Sie leicht ein Makro erstellen, das eine durch Kommas getrennte Liste von in Anführungszeichen eingeschlossenen Elementen erstellt (siehe zum Beispiel BOOST_PP_SEQ_ENUM ), aber ich glaube, die Eingabe dieses Makros muss eine von sein Diese Typen und alle erfordern, dass die Zeichen einzeln eingegeben werden.

3
icecrime

Basierend auf dem, was ich oben besprochen habe, kann die folgende schreckliche Schablonenhackerei ausreichen, um dies durchzuziehen. Ich habe das nicht getestet (sorry!), Aber ich bin mir ziemlich sicher, dass es funktioniert oder etwas in der Nähe davon.

Der erste Schritt besteht darin, eine Vorlagenklasse zu erstellen, die nur ein Tupel Zeichen enthält:

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

Erstellen wir nun einen Adapter, der einen C-String in ein CharTuple umwandeln kann. Dazu benötigen wir die folgende Hilfsklasse, die im Grunde genommen ein LISP-ähnliches Problem für Tupel ist:

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

Nehmen wir auch an, wir haben eine Meta-If-Anweisung:

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

Mit dem folgenden Befehl können Sie eine Zeichenfolge im C-Stil in ein Tupel konvertieren:

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

Nachdem Sie eine Zeichenfolge im C-Stil in ein Tupel Zeichen konvertieren können, können Sie Ihre Eingabezeichenfolge durch diesen Typ leiten, um das Tupel wiederherzustellen. Wir werden jedoch ein bisschen mehr Maschinen brauchen, um dies zum Laufen zu bringen. Macht TMP nicht Spaß? :-)

Der erste Schritt ist, den ursprünglichen Code zu übernehmen:

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

und verwenden Sie einige Template-Spezialisierung, um es zu konvertieren

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

Es ist nur eine weitere Ebene der Indirektion. nichts mehr.

Schließlich sollten Sie in der Lage sein, dies zu tun:

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

Ich hoffe wirklich, dass dies funktioniert. Wenn nicht, denke ich immer noch, dass es sich lohnt, dies zu posten, da es wahrscheinlich einer gültigen Antwort sehr nahe kommt. :-)

2
templatetypedef

Basierend auf der obigen Lösung von user1653543 .

Einige Vorlagenmagie:

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;

Etwas PP Magie:

#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 hilft nur beim Schneiden von nachgestellten Nullen. Jetzt ist STRING("Hello") eine typisierte Zeichenfolge zur Kompilierungszeit (std::integer_sequence<char, 'H', 'e', 'l', 'l', 'o'>). Die Länge der Zeichenfolgenkonstanten beträgt maximal BOOST_PP_LIMIT_REPEAT Zeichen.

Hausaufgaben : Implementiere Push_front_t und c_str, um eine nullterminierte Zeichenfolge von std::integer_sequence<char, ...> zu erhalten. (Sie können jedoch versuchen, Boost.MPL zu verwenden.)

2
Nevermore

In C++ 14 kann dies mit einer sofort aufgerufenen Lambda-Funktion und einer statischen Member-Funktion erfolgen, ähnlich wie in 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 auf Godbolt

Vor C++ 17 kann das von STR("some literal") zurückgegebene Objekt nicht constexpr sein, da das Lambda nicht constexpr sein kann. Vor C++ 20 können Sie nicht einfach decltype(STR("some literal")) schreiben, da Lambdas in nicht bewerteten Kontexten nicht zulässig sind.

0
Justin