Bequemes Deklarieren von Zeichenfolgen zur Kompilierungszeit in C ++

137

Das Erstellen und Bearbeiten von Zeichenfolgen während der Kompilierungszeit in C ++ bietet mehrere nützliche Anwendungen. Obwohl es möglich ist, Zeichenfolgen zur Kompilierungszeit in C ++ zu erstellen, ist der Prozess sehr umständlich, da die Zeichenfolge als variable Zeichenfolge deklariert werden muss, z

using str = sequence<'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!'>;

Operationen wie die Verkettung von Zeichenfolgen, das Extrahieren von Teilzeichenfolgen und viele andere können einfach als Operationen für Zeichenfolgen implementiert werden. Ist es möglich, Zeichenfolgen zur Kompilierungszeit bequemer zu deklarieren? Wenn nicht, gibt es einen Vorschlag in den Arbeiten, der eine bequeme Deklaration von Zeichenfolgen zur Kompilierungszeit ermöglichen würde?

Warum bestehende Ansätze fehlschlagen

Im Idealfall möchten wir in der Lage sein, Zeichenfolgen zur Kompilierungszeit wie folgt zu deklarieren:

// Approach 1
using str1 = sequence<"Hello, world!">;

oder unter Verwendung benutzerdefinierter Literale

// Approach 2
constexpr auto str2 = "Hello, world!"_s;

wo decltype(str2)hätte ein constexprKonstruktor. Eine chaotischere Version von Ansatz 1 kann implementiert werden, wobei die Tatsache ausgenutzt wird, dass Sie Folgendes tun können:

template <unsigned Size, const char Array[Size]>
struct foo;

Das Array müsste jedoch über eine externe Verknüpfung verfügen. Damit Ansatz 1 funktioniert, müssten wir Folgendes schreiben:

/* Implementation of array to sequence goes here. */

constexpr const char str[] = "Hello, world!";

int main()
{
    using s = string<13, str>;
    return 0;
}

Das ist natürlich sehr unpraktisch. Ansatz 2 ist eigentlich nicht umsetzbar. Wenn wir einen ( constexpr) Literaloperator deklarieren würden, wie würden wir dann den Rückgabetyp angeben? Da der Operator eine variable Folge von Zeichen zurückgeben muss, müssten wir den const char*Parameter verwenden, um den Rückgabetyp anzugeben:

constexpr auto
operator"" _s(const char* s, size_t n) -> /* Some metafunction using `s` */

Dies führt zu einem Kompilierungsfehler, da ses sich nicht um einen handelt constexpr. Der Versuch, dies mit den folgenden Schritten zu umgehen, hilft nicht viel.

template <char... Ts>
constexpr sequence<Ts...> operator"" _s() { return {}; }

Der Standard schreibt vor, dass diese spezielle Literaloperatorform für Ganzzahl- und Gleitkommatypen reserviert ist. Während 123_swürde funktionieren, abc_swürde nicht. Was ist, wenn wir benutzerdefinierte Literale ganz weglassen und nur eine reguläre constexprFunktion verwenden?

template <unsigned Size>
constexpr auto
string(const char (&array)[Size]) -> /* Some metafunction using `array` */

Nach wie vor stoßen wir auf das Problem, dass das Array, jetzt ein Parameter für die constexprFunktion, selbst kein constexprTyp mehr ist .

Ich glaube, es sollte möglich sein, ein C-Präprozessor-Makro zu definieren, das eine Zeichenfolge und die Größe der Zeichenfolge als Argumente verwendet und eine Sequenz zurückgibt, die aus den Zeichen in der Zeichenfolge besteht (using BOOST_PP_FOR, stringification, array subscripts und dergleichen). Ich habe jedoch nicht die Zeit (oder nicht genug Interesse), um ein solches Makro zu implementieren =)

Leerzeiger
quelle
2
Boost verfügt über ein Makro, das eine Zeichenfolge definiert, die als konstanter Ausdruck verwendet werden kann. Nun, es definiert eine Klasse, die ein String-Mitglied hat. Hast du das überprüft?
Pubby
6
Haben Sie cpp-next.com/archive/2012/10/… überprüft ?
Evgeny Panasyuk
1
Der Stapelüberlauf ist nicht der geeignete Ort, um zu fragen, ob ein Vorschlag für etwas vorhanden ist. Der beste Ort dafür wäre die C ++ - Site .
Nicol Bolas
1
Grundsätzlich erweitern Sie die im Array / ptr gespeicherten Zeichen zu einem Parameterpaket (wie Xeo). Obwohl sie nicht in nicht constexprtypisierte Vorlagenargumente unterteilt sind, können Sie sie in Funktionen verwenden und Arrays (daher concat, substr usw.) initialisieren.
Dyp
1
@MareInfinitus Kurz gesagt, constexprZeichenfolgen können während der Kompilierungszeit analysiert werden, sodass Sie je nach Ergebnis unterschiedliche Codepfade verwenden können. Im Wesentlichen können Sie EDLs in C ++ erstellen. Die Anwendungen sind ziemlich grenzenlos.
void-pointer

Antworten:

127

Ich habe nichts gesehen, was der Eleganz von Scott Schurrstr_const auf der C ++ Now 2012 entspricht . Es erfordert constexprjedoch.

So können Sie es verwenden und was es kann:

int
main()
{
    constexpr str_const my_string = "Hello, world!";
    static_assert(my_string.size() == 13, "");
    static_assert(my_string[4] == 'o', "");
    constexpr str_const my_other_string = my_string;
    static_assert(my_string == my_other_string, "");
    constexpr str_const world(my_string, 7, 5);
    static_assert(world == "world", "");
//  constexpr char x = world[5]; // Does not compile because index is out of range!
}

Es wird nicht viel cooler als die Überprüfung der Kompilierungszeit!

Sowohl die Verwendung als auch die Implementierung sind frei von Makros. Und es gibt keine künstliche Begrenzung für die Saitengröße. Ich würde die Implementierung hier veröffentlichen, aber ich respektiere Scotts implizites Urheberrecht. Die Implementierung befindet sich auf einer einzelnen Folie seiner Präsentation, die oben verlinkt ist.

Howard Hinnant
quelle
3
Können Operationen, die neue constexpr-Zeichenfolgen erstellen (z. B. Verkettung von Zeichenfolgen und Extraktion von Teilzeichenfolgen), mit diesem Ansatz funktionieren? Möglicherweise ist dies mit zwei constexpr-string-Klassen möglich (eine basierend auf str_constund die andere basierend auf sequence). Der Benutzer würde str_constdie Zeichenfolge initialisieren, aber nachfolgende Vorgänge, die neue Zeichenfolgen erstellen, würden sequenceObjekte zurückgeben.
void-pointer
5
Dies ist ein guter Code. Dieser Ansatz weist jedoch immer noch einen Fehler im Vergleich zu einer Zeichenfolge auf, die mit einer Zeichenfolge als Vorlagenparameter deklariert wurde: a str_const ist ein konstanter Wert und kein Typ, wodurch die Verwendung vieler Metaprogrammierungs-Idiome verhindert wird.
Jean-Bernard Jansen
1
@JBJansen, es ist ohne Hash-Funktionen möglich, eine Zeichenfolge zu einem Typ zu kompilieren, der dann als Vorlagenparameter verwendet werden kann. Jede andere Zeichenfolge gibt einen anderen Typ an. Die Grundidee besteht darin, die Zeichenfolge in ein Zeichenpaket umzuwandeln template<char... cs>. Theoretisch könnten Sie etwas erstellen, das eine Literalzeichenfolge verwendet und den Inhalt zu einer Funktion kompiliert. Siehe die Antwort von dyp. Eine sehr vollständig aussehende Bibliothek ist metaparse . Im Wesentlichen können Sie jede Zuordnung von Literalzeichenfolgen zu Typen definieren und mit dieser Technologie implementieren.
Aaron McDaid
1
Ich teile die Begeisterung nicht… funktioniert nicht mit Vorlagen-Metafunktionen - sehr ärgerlich wegen des albernen Kompromisses, dass constexpr-Funktionen zur Laufzeit aufgerufen werden sollen - keine echte Verkettung, erfordert die Definition eines char-Arrays (hässlich im Header) - obwohl dies Dies gilt für die meisten makellosen Lösungen dank des oben genannten constexpr-Kompromisses - und die Bereichsprüfung beeindruckt mich nicht sehr, da selbst das niedrigste constexpr const char * dies hat. Ich habe meine eigene Parameterpaketzeichenfolge gerollt, die auch aus einem Literal (unter Verwendung einer Metafunktion) auf Kosten einer Arraydefinition erstellt werden kann.
Arne Vogel
2
@ user975326: Ich habe gerade meine Implementierung überprüft und es sieht so aus, als hätte ich eine hinzugefügt constexpr operator==. Es tut uns leid. Scotts Präsentation soll Ihnen den Einstieg erleichtern. In C ++ 14 ist dies viel einfacher als in C ++ 11. Ich würde nicht einmal die Mühe machen, es in C ++ 11 zu versuchen. Sehen Sie Scotts neueste constexprVorträge hier: youtube.com/user/CppCon
Howard Hinnant
41

Ich glaube, es sollte möglich sein, ein C-Präprozessor-Makro zu definieren, das eine Zeichenfolge und die Größe der Zeichenfolge als Argumente verwendet und eine Sequenz zurückgibt, die aus den Zeichen in der Zeichenfolge besteht (unter Verwendung von BOOST_PP_FOR, Zeichenfolge, Array-Indizes und dergleichen). Ich habe jedoch nicht die Zeit (oder das Interesse), ein solches Makro zu implementieren

Es ist möglich, dies ohne Boost zu implementieren, indem ein sehr einfaches Makro und einige der C ++ 11-Funktionen verwendet werden:

  1. Lambdas variadic
  2. Vorlagen
  3. verallgemeinerte konstante Ausdrücke
  4. Nicht statische Datenelementinitialisierer
  5. einheitliche Initialisierung

(Die beiden letzteren sind hier nicht unbedingt erforderlich)

  1. Wir müssen in der Lage sein, eine variadische Vorlage mit vom Benutzer angegebenen Angaben von 0 bis N zu instanziieren - ein Tool, das auch nützlich ist, um beispielsweise das Tupel in das Argument der variadischen Vorlagenfunktion zu erweitern (siehe Fragen: Wie erweitere ich ein Tupel in die Argumente der variadischen Vorlagenfunktion?
    " Auspacken "eines Tupels zum Aufrufen eines passenden Funktionszeigers )

    namespace  variadic_toolbox
    {
        template<unsigned  count, 
            template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range
        {
            typedef  typename apply_range<count-1, meta_functor, count-1, indices...>::result  result;
        };
    
        template<template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range<0, meta_functor, indices...>
        {
            typedef  typename meta_functor<indices...>::result  result;
        };
    }
  2. Definieren Sie dann eine variable Vorlage namens string mit dem Parameter char vom Typ non:

    namespace  compile_time
    {
        template<char...  str>
        struct  string
        {
            static  constexpr  const char  chars[sizeof...(str)+1] = {str..., '\0'};
        };
    
        template<char...  str>
        constexpr  const char  string<str...>::chars[sizeof...(str)+1];
    }
  3. jetzt der interessanteste Teil - Zeichenliterale in Zeichenfolgenvorlage zu übergeben:

    namespace  compile_time
    {
        template<typename  lambda_str_type>
        struct  string_builder
        {
            template<unsigned... indices>
            struct  produce
            {
                typedef  string<lambda_str_type{}.chars[indices]...>  result;
            };
        };
    }
    
    #define  CSTRING(string_literal)                                                        \
        []{                                                                                 \
            struct  constexpr_string_type { const char * chars = string_literal; };         \
            return  variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
                compile_time::string_builder<constexpr_string_type>::produce>::result{};    \
        }()

Eine einfache Verkettungsdemonstration zeigt die Verwendung:

    namespace  compile_time
    {
        template<char...  str0, char...  str1>
        string<str0..., str1...>  operator*(string<str0...>, string<str1...>)
        {
            return  {};
        }
    }

    int main()
    {
        auto  str0 = CSTRING("hello");
        auto  str1 = CSTRING(" world");

        std::cout << "runtime concat: " <<  str_hello.chars  << str_world.chars  << "\n <=> \n";
        std::cout << "compile concat: " <<  (str_hello * str_world).chars  <<  std::endl;
    }

https://ideone.com/8Ft2xu

user1115339
quelle
1
Das ist so einfach, dass ich immer noch nicht glauben kann, dass es funktioniert. +1! Eine Sache: Sollten Sie nicht size_t anstelle von unsigned verwenden?
Kirbyfan64sos
1
Und was ist mit operator+statt operator*? (str_hello + str_world)
Remy Lebeau
Ich bevorzuge diese Lösung gegenüber der beliebten str_const-Methode von Scott Schurr, da diese Methode sicherstellt, dass die zugrunde liegenden Daten constexpr sind. Mit der Schurr-Methode kann ich zur Laufzeit eine str_const mit einer char [] -Stackvariablen erstellen. Ich kann eine str_const nicht sicher von einer Funktion zurückgeben oder an einen anderen Thread übergeben.
Glenn
Der Link ist tot ... kann jemand ihn erneut veröffentlichen? @Glenn?
Einpoklum
Sie sollten ein zusätzliches Paar Klammern um das Lambda in Ihrem CSTRINGMakro hinzufügen . Andernfalls können Sie CSTRINGinnerhalb eines Aufrufs einen []Operator nicht erstellen , da double [[für Attribute reserviert ist.
Florestan
21

Bearbeiten: Wie Howard Hinnant (und ich in meinem Kommentar zum OP) betonten, benötigen Sie möglicherweise nicht einen Typ mit jedem einzelnen Zeichen der Zeichenfolge als einzelnes Vorlagenargument. Wenn Sie dies benötigen, finden Sie unten eine makrofreie Lösung.

Es gibt einen Trick, den ich beim Kompilieren mit Strings gefunden habe. Neben der "Vorlagenzeichenfolge" muss ein anderer Typ eingeführt werden. Innerhalb von Funktionen können Sie jedoch den Umfang dieses Typs einschränken.

Es werden keine Makros verwendet, sondern einige C ++ 11-Funktionen.

#include <iostream>

// helper function
constexpr unsigned c_strlen( char const* str, unsigned count = 0 )
{
    return ('\0' == str[0]) ? count : c_strlen(str+1, count+1);
}

// helper "function" struct
template < char t_c, char... tt_c >
struct rec_print
{
    static void print()
    {
        std::cout << t_c;
        rec_print < tt_c... > :: print ();
    }
};
    template < char t_c >
    struct rec_print < t_c >
    {
        static void print() { std::cout << t_c; }
    };


// destination "template string" type
template < char... tt_c >
struct exploded_string
{
    static void print()
    {
        rec_print < tt_c... > :: print();
    }
};

// struct to explode a `char const*` to an `exploded_string` type
template < typename T_StrProvider, unsigned t_len, char... tt_c >
struct explode_impl
{
    using result =
        typename explode_impl < T_StrProvider, t_len-1,
                                T_StrProvider::str()[t_len-1],
                                tt_c... > :: result;
};

    template < typename T_StrProvider, char... tt_c >
    struct explode_impl < T_StrProvider, 0, tt_c... >
    {
         using result = exploded_string < tt_c... >;
    };

// syntactical sugar
template < typename T_StrProvider >
using explode =
    typename explode_impl < T_StrProvider,
                            c_strlen(T_StrProvider::str()) > :: result;


int main()
{
    // the trick is to introduce a type which provides the string, rather than
    // storing the string itself
    struct my_str_provider
    {
        constexpr static char const* str() { return "hello world"; }
    };

    auto my_str = explode < my_str_provider >{};    // as a variable
    using My_Str = explode < my_str_provider >;    // as a type

    my_str.print();
}
dyp
quelle
1
Ich habe gerade das Wochenende damit verbracht, unabhängig einen ähnlichen Code zu entwickeln und ein sehr einfaches System zum Parsen von Zeichenfolgen zu erstellen, z pair<int,pair<char,double>>. Ich war stolz auf mich und entdeckte dann diese Antwort und die Metaparse- Bibliothek heute! Ich sollte wirklich SO gründlicher suchen, bevor ich dumme Projekte wie dieses starte :-) Ich denke, dass theoretisch ein vollständig C ++ - Compiler aus dieser Art von Technologie erstellt werden könnte. Was ist das Verrückteste, das damit gebaut wurde?
Aaron McDaid
Ich weiß es nicht. Ich habe diese Techniken nie wirklich in einem realen Projekt verwendet, also habe ich den Ansatz nicht verfolgt. Obwohl ich denke, ich erinnere mich an eine geringfügige Variation des Tricks vom lokalen Typ, die etwas praktischer war. Vielleicht eine lokale statische Aufladung char[].
Dyp
Meinst du my_str.print();statt str.print();?
Mike
Gibt es eine etwas kürzere C ++ 14-Version?
Mike
Es ist eine Schande, dass Sie den Anbieter machen müssen (zumindest in C ++ 11) - Ich möchte wirklich in der Lage sein, eine Zeichenfolge in derselben Anweisung zu verwenden: /
Alec Teal
10

Wenn Sie die Boost-Lösung nicht verwenden möchten, können Sie einfache Makros erstellen, die ähnliche Aktionen ausführen:

#define MACRO_GET_1(str, i) \
    (sizeof(str) > (i) ? str[(i)] : 0)

#define MACRO_GET_4(str, i) \
    MACRO_GET_1(str, i+0),  \
    MACRO_GET_1(str, i+1),  \
    MACRO_GET_1(str, i+2),  \
    MACRO_GET_1(str, i+3)

#define MACRO_GET_16(str, i) \
    MACRO_GET_4(str, i+0),   \
    MACRO_GET_4(str, i+4),   \
    MACRO_GET_4(str, i+8),   \
    MACRO_GET_4(str, i+12)

#define MACRO_GET_64(str, i) \
    MACRO_GET_16(str, i+0),  \
    MACRO_GET_16(str, i+16), \
    MACRO_GET_16(str, i+32), \
    MACRO_GET_16(str, i+48)

#define MACRO_GET_STR(str) MACRO_GET_64(str, 0), 0 //guard for longer strings

using seq = sequence<MACRO_GET_STR("Hello world!")>;

Das einzige Problem ist die feste Größe von 64 Zeichen (plus zusätzliche Null). Es kann jedoch leicht je nach Ihren Anforderungen geändert werden.

Ruckelt
quelle
Ich mag diese Lösung sehr; Es ist sehr einfach und erledigt die Arbeit elegant. Ist es möglich, das Makro so zu ändern, dass nichts angehängt wird sizeof(str) > i(anstatt die zusätzlichen 0,Token anzuhängen)? Es ist einfach, eine trimMetafunktion zu definieren , die dies tut, nachdem das Makro bereits aufgerufen wurde, aber es wäre schön, wenn das Makro selbst geändert werden könnte.
hohlraum Zeiger
Ist unmöglich, weil Parser nicht verstehen sizeof(str). Es ist möglich, die Zeichenfolgengröße manuell hinzuzufügen MACRO_GET_STR(6, "Hello"), dies erfordert jedoch Boost-Makros, da das manuelle Schreiben 100-mal mehr Code erfordert (Sie müssen einfache Dinge wie implementieren 1+1).
Yankes
6

Ich glaube, es sollte möglich sein, ein C-Präprozessor-Makro zu definieren, das eine Zeichenfolge und die Größe der Zeichenfolge als Argumente verwendet und eine Sequenz zurückgibt, die aus den Zeichen in der Zeichenfolge besteht (unter Verwendung von BOOST_PP_FOR, Zeichenfolge, Array-Indizes und dergleichen).

Es gibt einen Artikel: Verwenden von Zeichenfolgen in C ++ - Vorlagenmetaprogrammen von Abel Sinkovics und Dave Abrahams.

Es hat einige Verbesserungen gegenüber Ihrer Idee, Makro + BOOST_PP_REPEAT zu verwenden - es ist nicht erforderlich, eine explizite Größe an das Makro zu übergeben. Kurz gesagt, es basiert auf einer festen Obergrenze für die Zeichenfolgengröße und dem "Schutz vor Zeichenfolgenüberlauf":

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

plus bedingter Boost :: mpl :: push_back .


Ich habe meine akzeptierte Antwort auf Yankes 'Lösung geändert, da sie dieses spezifische Problem löst und dies elegant ohne die Verwendung von constexpr oder komplexem Präprozessorcode.

Wenn Sie nachfolgende Nullen annehmen, handgeschriebenen Makro Looping, 2x repetion Schnur in der erweiterten Makro, und nicht - Boost haben - dann stimme ich - es ist besser. Bei Boost wären es jedoch nur drei Zeilen:

LIVE DEMO

#include <boost/preprocessor/repetition/repeat.hpp>
#define GET_STR_AUX(_, i, str) (sizeof(str) > (i) ? str[(i)] : 0),
#define GET_STR(str) BOOST_PP_REPEAT(64,GET_STR_AUX,str) 0
Evgeny Panasyuk
quelle
Ich habe die Lösung zunächst auf Yankes geändert, da er hier das erste Arbeitsbeispiel lieferte. Zu diesem Zeitpunkt gibt es viele gute konkurrierende Ideen. Es war mein Fehler, so früh eine Antwort zu finden. Ich werde diese Frage derzeit als unbeantwortet bemerken und warten, bis ich die Zeit habe, die Ideen auszuprobieren, die alle hier gepostet haben. Es gibt viele nützliche Informationen in den Antworten, die die Leute hier gegeben haben ...
void-pointer
Ich stimme zu - zum Beispiel mag ich das Beispiel von Howard Hinnant.
Evgeny Panasyuk
5

Niemand scheint meine andere Antwort zu mögen: - <. Hier zeige ich also, wie man eine str_const in einen realen Typ konvertiert:

#include <iostream>
#include <utility>

// constexpr string with const member functions
class str_const { 
private:
    const char* const p_;
    const std::size_t sz_;
public:

    template<std::size_t N>
    constexpr str_const(const char(&a)[N]) : // ctor
    p_(a), sz_(N-1) {}

    constexpr char operator[](std::size_t n) const { 
        return n < sz_ ? p_[n] :
        throw std::out_of_range("");
    }

    constexpr std::size_t size() const { return sz_; } // size()
};


template <char... letters>
struct string_t{
    static char const * c_str() {
        static constexpr char string[]={letters...,'\0'};
        return string;
    }
};

template<str_const const& str,std::size_t... I>
auto constexpr expand(std::index_sequence<I...>){
    return string_t<str[I]...>{};
}

template<str_const const& str>
using string_const_to_type = decltype(expand<str>(std::make_index_sequence<str.size()>{}));

constexpr str_const hello{"Hello World"};
using hello_t = string_const_to_type<hello>;

int main()
{
//    char c = hello_t{};        // Compile error to print type
    std::cout << hello_t::c_str();
    return 0;
}

Kompiliert mit clang ++ -stdlib = libc ++ -std = c ++ 14 (clang 3.7)

Netter Mann
quelle
Funktioniert gut, aber nicht für msvc 2019, da es sich darüber beschwert, dass str.size () nicht constexpr ist. Kann durch Hinzufügen einer zweiten durch separate Ableitung von str.size () behoben werden. Vielleicht hat das ein paar positive Stimmen zurückgehalten ;-)
Zacharias
4

Hier ist eine kurze C ++ 14-Lösung zum Erstellen eines std :: tuple <char ...> für jede übergebene Zeichenfolge zur Kompilierungszeit.

#include <tuple>
#include <utility>


namespace detail {
        template <std::size_t ... indices>
        decltype(auto) build_string(const char * str, std::index_sequence<indices...>) {
                return std::make_tuple(str[indices]...);
        }
}

template <std::size_t N>
constexpr decltype(auto) make_string(const char(&str)[N]) {
        return detail::build_string(str, std::make_index_sequence<N>());
}

auto HelloStrObject = make_string("hello");

Und hier ist eine zum Erstellen eines eindeutigen Typs zur Kompilierungszeit, der vom anderen Makropost abgeschnitten wurde.

#include <utility>

template <char ... Chars>
struct String {};

template <typename Str, std::size_t ... indices>
decltype(auto) build_string(std::index_sequence<indices...>) {
        return String<Str().chars[indices]...>();
}

#define make_string(str) []{\
        struct Str { const char * chars = str; };\
        return build_string<Str>(std::make_index_sequence<sizeof(str)>());\
}()

auto HelloStrObject = make_string("hello");

Es ist wirklich schade, dass benutzerdefinierte Literale dafür noch nicht verwendet werden können.

kacey
quelle
Eigentlich können sie eine von GCC / Clang unterstützte Erweiterung verwenden, aber ich werde warten, bis diese zum Standard hinzugefügt wird, bevor ich sie als Antwort veröffentliche.
Void-Pointer
3

Ein Kollege forderte mich auf, Zeichenfolgen im Speicher zur Kompilierungszeit zu verketten. Es umfasst auch das Instanziieren einzelner Zeichenfolgen zur Kompilierungszeit. Die vollständige Codeliste finden Sie hier:

//Arrange strings contiguously in memory at compile-time from string literals.
//All free functions prefixed with "my" to faciliate grepping the symbol tree
//(none of them should show up).

#include <iostream>

using std::size_t;

//wrapper for const char* to "allocate" space for it at compile-time
template<size_t N>
struct String {
    //C arrays can only be initialised with a comma-delimited list
    //of values in curly braces. Good thing the compiler expands
    //parameter packs into comma-delimited lists. Now we just have
    //to get a parameter pack of char into the constructor.
    template<typename... Args>
    constexpr String(Args... args):_str{ args... } { }
    const char _str[N];
};

//takes variadic number of chars, creates String object from it.
//i.e. myMakeStringFromChars('f', 'o', 'o', '\0') -> String<4>::_str = "foo"
template<typename... Args>
constexpr auto myMakeStringFromChars(Args... args) -> String<sizeof...(Args)> {
    return String<sizeof...(args)>(args...);
}

//This struct is here just because the iteration is going up instead of
//down. The solution was to mix traditional template metaprogramming
//with constexpr to be able to terminate the recursion since the template
//parameter N is needed in order to return the right-sized String<N>.
//This class exists only to dispatch on the recursion being finished or not.
//The default below continues recursion.
template<bool TERMINATE>
struct RecurseOrStop {
    template<size_t N, size_t I, typename... Args>
    static constexpr String<N> recurseOrStop(const char* str, Args... args);
};

//Specialisation to terminate recursion when all characters have been
//stripped from the string and converted to a variadic template parameter pack.
template<>
struct RecurseOrStop<true> {
    template<size_t N, size_t I, typename... Args>
    static constexpr String<N> recurseOrStop(const char* str, Args... args);
};

//Actual function to recurse over the string and turn it into a variadic
//parameter list of characters.
//Named differently to avoid infinite recursion.
template<size_t N, size_t I = 0, typename... Args>
constexpr String<N> myRecurseOrStop(const char* str, Args... args) {
    //template needed after :: since the compiler needs to distinguish
    //between recurseOrStop being a function template with 2 paramaters
    //or an enum being compared to N (recurseOrStop < N)
    return RecurseOrStop<I == N>::template recurseOrStop<N, I>(str, args...);
}

//implementation of the declaration above
//add a character to the end of the parameter pack and recurse to next character.
template<bool TERMINATE>
template<size_t N, size_t I, typename... Args>
constexpr String<N> RecurseOrStop<TERMINATE>::recurseOrStop(const char* str,
                                                            Args... args) {
    return myRecurseOrStop<N, I + 1>(str, args..., str[I]);
}

//implementation of the declaration above
//terminate recursion and construct string from full list of characters.
template<size_t N, size_t I, typename... Args>
constexpr String<N> RecurseOrStop<true>::recurseOrStop(const char* str,
                                                       Args... args) {
    return myMakeStringFromChars(args...);
}

//takes a compile-time static string literal and returns String<N> from it
//this happens by transforming the string literal into a variadic paramater
//pack of char.
//i.e. myMakeString("foo") -> calls myMakeStringFromChars('f', 'o', 'o', '\0');
template<size_t N>
constexpr String<N> myMakeString(const char (&str)[N]) {
    return myRecurseOrStop<N>(str);
}

//Simple tuple implementation. The only reason std::tuple isn't being used
//is because its only constexpr constructor is the default constructor.
//We need a constexpr constructor to be able to do compile-time shenanigans,
//and it's easier to roll our own tuple than to edit the standard library code.

//use MyTupleLeaf to construct MyTuple and make sure the order in memory
//is the same as the order of the variadic parameter pack passed to MyTuple.
template<typename T>
struct MyTupleLeaf {
    constexpr MyTupleLeaf(T value):_value(value) { }
    T _value;
};

//Use MyTupleLeaf implementation to define MyTuple.
//Won't work if used with 2 String<> objects of the same size but this
//is just a toy implementation anyway. Multiple inheritance guarantees
//data in the same order in memory as the variadic parameters.
template<typename... Args>
struct MyTuple: public MyTupleLeaf<Args>... {
    constexpr MyTuple(Args... args):MyTupleLeaf<Args>(args)... { }
};

//Helper function akin to std::make_tuple. Needed since functions can deduce
//types from parameter values, but classes can't.
template<typename... Args>
constexpr MyTuple<Args...> myMakeTuple(Args... args) {
    return MyTuple<Args...>(args...);
}

//Takes a variadic list of string literals and returns a tuple of String<> objects.
//These will be contiguous in memory. Trailing '\0' adds 1 to the size of each string.
//i.e. ("foo", "foobar") -> (const char (&arg1)[4], const char (&arg2)[7]) params ->
//                       ->  MyTuple<String<4>, String<7>> return value
template<size_t... Sizes>
constexpr auto myMakeStrings(const char (&...args)[Sizes]) -> MyTuple<String<Sizes>...> {
    //expands into myMakeTuple(myMakeString(arg1), myMakeString(arg2), ...)
    return myMakeTuple(myMakeString(args)...);
}

//Prints tuple of strings
template<typename T> //just to avoid typing the tuple type of the strings param
void printStrings(const T& strings) {
    //No std::get or any other helpers for MyTuple, so intead just cast it to
    //const char* to explore its layout in memory. We could add iterators to
    //myTuple and do "for(auto data: strings)" for ease of use, but the whole
    //point of this exercise is the memory layout and nothing makes that clearer
    //than the ugly cast below.
    const char* const chars = reinterpret_cast<const char*>(&strings);
    std::cout << "Printing strings of total size " << sizeof(strings);
    std::cout << " bytes:\n";
    std::cout << "-------------------------------\n";

    for(size_t i = 0; i < sizeof(strings); ++i) {
        chars[i] == '\0' ? std::cout << "\n" : std::cout << chars[i];
    }

    std::cout << "-------------------------------\n";
    std::cout << "\n\n";
}

int main() {
    {
        constexpr auto strings = myMakeStrings("foo", "foobar",
                                               "strings at compile time");
        printStrings(strings);
    }

    {
        constexpr auto strings = myMakeStrings("Some more strings",
                                               "just to show Jeff to not try",
                                               "to challenge C++11 again :P",
                                               "with more",
                                               "to show this is variadic");
        printStrings(strings);
    }

    std::cout << "Running 'objdump -t |grep my' should show that none of the\n";
    std::cout << "functions defined in this file (except printStrings()) are in\n";
    std::cout << "the executable. All computations are done by the compiler at\n";
    std::cout << "compile-time. printStrings() executes at run-time.\n";
}
Átila Neves
quelle
Sind Sie sicher, dass dies zur Kompilierungszeit erfolgt? Vor einiger Zeit gab es eine Diskussion darüber , und für mich ist das Ergebnis nicht klar.
Dyp
Laufen objdump -t a.out |grep myfindet nichts. Als ich anfing, diesen Code einzugeben, experimentierte ich weiter mit dem Entfernen constexpraus den Funktionen und objdumpzeigte ihnen, wann sie constexprweggelassen wurden. Ich bin zu 99,9% davon überzeugt, dass dies zur Kompilierungszeit geschieht.
Átila Neves
1
Wenn Sie sich die Disassembly ( -S) ansehen , werden Sie feststellen, dass gcc (4.7.2) die constexprFunktionen zur Kompilierungszeit tatsächlich auflöst . Die Zeichenfolgen werden jedoch zur Kompilierungszeit nicht zusammengestellt. Vielmehr gibt es (wenn ich es richtig interpretiere) für jedes Zeichen dieser "zusammengesetzten" Zeichenfolgen eine eigene movbOperation, die wohl die Optimierung ist, nach der Sie gesucht haben.
Dyp
2
Das ist richtig. Ich habe es erneut mit gcc 4.9 versucht und es macht immer noch das Gleiche. Ich habe immer gedacht, dass dies der dumme Compiler ist. Erst gestern habe ich daran gedacht, einen anderen Compiler auszuprobieren. Mit clang sind die byteweisen Bewegungen überhaupt nicht da. Mit gcc werden sie auch von -Os entfernt, aber -O3 macht dasselbe.
Átila Neves
2

Basierend auf der Idee von Howard Hinnant können Sie eine Literalklasse erstellen, die zwei Literale addiert.

template<int>
using charDummy = char;

template<int... dummy>
struct F
{
    const char table[sizeof...(dummy) + 1];
    constexpr F(const char* a) : table{ str_at<dummy>(a)..., 0}
    {

    }
    constexpr F(charDummy<dummy>... a) : table{ a..., 0}
    {

    }

    constexpr F(const F& a) : table{ a.table[dummy]..., 0}
    {

    }

    template<int... dummyB>
    constexpr F<dummy..., sizeof...(dummy)+dummyB...> operator+(F<dummyB...> b)
    {
        return { this->table[dummy]..., b.table[dummyB]... };
    }
};

template<int I>
struct get_string
{
    constexpr static auto g(const char* a) -> decltype( get_string<I-1>::g(a) + F<0>(a + I))
    {
        return get_string<I-1>::g(a) + F<0>(a + I);
    }
};

template<>
struct get_string<0>
{
    constexpr static F<0> g(const char* a)
    {
        return {a};
    }
};

template<int I>
constexpr auto make_string(const char (&a)[I]) -> decltype( get_string<I-2>::g(a) )
{
    return get_string<I-2>::g(a);
}

constexpr auto a = make_string("abc");
constexpr auto b = a+ make_string("def"); // b.table == "abcdef" 
Ruckelt
quelle
woher str_atkommt das
mic_e
es ist so ähnlich:str_at<int I>(const char* a) { return a[i]; }
Yankes
2

Ihr Ansatz Nr. 1 ist der richtige.

Das Array müsste jedoch über eine externe Verknüpfung verfügen. Damit Ansatz 1 funktioniert, müssten wir Folgendes schreiben: constexpr const char str [] = "Hallo Welt!";

Nein, nicht richtig. Dies wird mit clang und gcc kompiliert. Ich hoffe, es ist Standard C ++ 11, aber ich bin kein Sprachschreiber.

#include <iostream>

template <char... letters>
struct string_t{
    static char const * c_str() {
        static constexpr char string[]={letters...,'\0'};
        return string;
    }
};

// just live with it, but only once
using Hello_World_t = string_t<'H','e','l','l','o',' ','w','o','r','l','d','!'>;

template <typename Name>
void print()
{
    //String as template parameter
    std::cout << Name::c_str();
}

int main() {
    std::cout << Hello_World_t::c_str() << std::endl;
    print<Hello_World_t>();
    return 0;
}

Was ich für c ++ 17 wirklich lieben würde, wäre das Folgende, um gleichwertig zu sein (um Ansatz 1 zu vervollständigen)

// for template <char...>
<"Text"> == <'T','e','x','t'>

Etwas sehr Ähnliches existiert bereits im Standard für benutzerdefinierte Vorlagenliterale, wie der Void-Zeiger ebenfalls erwähnt, jedoch nur für Ziffern. Bis dahin besteht ein weiterer kleiner Trick darin, den Überschreibungsbearbeitungsmodus + Kopieren und Einfügen von zu verwenden

string_t<' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '>;

Wenn Ihnen das Makro nichts ausmacht, funktioniert dies (geringfügig geändert von Yankes Antwort):

#define MACRO_GET_1(str, i) \
(sizeof(str) > (i) ? str[(i)] : 0)

#define MACRO_GET_4(str, i) \
MACRO_GET_1(str, i+0),  \
MACRO_GET_1(str, i+1),  \
MACRO_GET_1(str, i+2),  \
MACRO_GET_1(str, i+3)

#define MACRO_GET_16(str, i) \
MACRO_GET_4(str, i+0),   \
MACRO_GET_4(str, i+4),   \
MACRO_GET_4(str, i+8),   \
MACRO_GET_4(str, i+12)

#define MACRO_GET_64(str, i) \
MACRO_GET_16(str, i+0),  \
MACRO_GET_16(str, i+16), \
MACRO_GET_16(str, i+32), \
MACRO_GET_16(str, i+48)

//CT_STR means Compile-Time_String
#define CT_STR(str) string_t<MACRO_GET_64(#str, 0), 0 >//guard for longer strings

print<CT_STR(Hello World!)>();
Netter Mann
quelle
2

Die Lösung von kacey zum Erstellen eines eindeutigen Typs zur Kompilierungszeit kann mit geringfügigen Änderungen auch mit C ++ 11 verwendet werden:

template <char... Chars>
struct string_t {};

namespace detail {
template <typename Str,unsigned int N,char... Chars>
struct make_string_t : make_string_t<Str,N-1,Str().chars[N-1],Chars...> {};

template <typename Str,char... Chars>
struct make_string_t<Str,0,Chars...> { typedef string_t<Chars...> type; };
} // namespace detail

#define CSTR(str) []{ \
    struct Str { const char *chars = str; }; \
    return detail::make_string_t<Str,sizeof(str)>::type(); \
  }()

Verwenden:

template <typename String>
void test(String) {
  // ... String = string_t<'H','e','l','l','o','\0'>
}

test(CSTR("Hello"));
lächelnderax
quelle
2

Beim Spielen mit der Boost-Hana-Karte bin ich auf diesen Thread gestoßen. Da keine der Antworten mein Problem löste, fand ich eine andere Lösung, die ich hier hinzufügen möchte, da sie möglicherweise für andere hilfreich sein könnte.

Mein Problem war, dass der Compiler bei Verwendung der Boost-Hana-Map mit Hana-Strings immer noch Laufzeitcode generierte (siehe unten). Der Grund war offensichtlich, dass es sein muss, um die Karte zur Kompilierungszeit abzufragen constexpr. Dies ist nicht möglich, da das BOOST_HANA_STRINGMakro ein Lambda generiert, das nicht im constexprKontext verwendet werden kann. Andererseits benötigt die Karte Zeichenfolgen mit unterschiedlichem Inhalt, um unterschiedliche Typen zu sein.

Da die Lösungen in diesem Thread entweder ein Lambda verwenden oder keine unterschiedlichen Typen für unterschiedliche Inhalte bereitstellen, fand ich den folgenden Ansatz hilfreich. Außerdem wird die hackige str<'a', 'b', 'c'>Syntax vermieden .

Die Grundidee ist, eine Version von Scott Schurrs str_constVorlage auf dem Hash der Charaktere zu haben. Dies ist möglich c++14, c++11sollte aber mit einer rekursiven Implementierung der crc32Funktion möglich sein (siehe hier ).

// str_const from https://github.com/boostcon/cppnow_presentations_2012/blob/master/wed/schurr_cpp11_tools_for_class_authors.pdf?raw=true

    #include <string>

template<unsigned Hash>  ////// <- This is the difference...
class str_const2 { // constexpr string
private:
    const char* const p_;
    const std::size_t sz_;
public:
    template<std::size_t N>
    constexpr str_const2(const char(&a)[N]) : // ctor
        p_(a), sz_(N - 1) {}


    constexpr char operator[](std::size_t n) const { // []
        return n < sz_ ? p_[n] :
            throw std::out_of_range("");
    }

    constexpr std::size_t size() const { return sz_; } // size()

    constexpr const char* const data() const {
        return p_;
    }
};

// Crc32 hash function. Non-recursive version of https://stackoverflow.com/a/23683218/8494588
static constexpr unsigned int crc_table[256] = {
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

template<size_t N>
constexpr auto crc32(const char(&str)[N])
{
    unsigned int prev_crc = 0xFFFFFFFF;
    for (auto idx = 0; idx < sizeof(str) - 1; ++idx)
        prev_crc = (prev_crc >> 8) ^ crc_table[(prev_crc ^ str[idx]) & 0xFF];
    return prev_crc ^ 0xFFFFFFFF;
}

// Conveniently create a str_const2
#define CSTRING(text) str_const2 < crc32( text ) >( text )

// Conveniently create a hana type_c<str_const2> for use in map
#define CSTRING_TYPE(text) hana::type_c<decltype(str_const2 < crc32( text ) >( text ))>

Verwendung:

#include <boost/hana.hpp>

#include <boost/hana/map.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/type.hpp>

namespace hana = boost::hana;

int main() {

    constexpr auto s2 = CSTRING("blah");

    constexpr auto X = hana::make_map(
        hana::make_pair(CSTRING_TYPE("aa"), 1)
    );    
    constexpr auto X2 = hana::insert(X, hana::make_pair(CSTRING_TYPE("aab"), 2));   
    constexpr auto ret = X2[(CSTRING_TYPE("aab"))];
    return ret;
}

Der resultierende Assembler-Code mit clang-cl5.0 lautet:

012A1370  mov         eax,2  
012A1375  ret  
florestan
quelle
0

Ich möchte der Antwort von @ user1115339 zwei sehr kleine Verbesserungen hinzufügen. Ich habe sie in den Kommentaren zur Antwort erwähnt, aber der Einfachheit halber werde ich hier eine Lösung zum Kopieren und Einfügen einfügen.

Der einzige Unterschied ist das FIXED_CSTRINGMakro, mit dem die Zeichenfolgen in Klassenvorlagen und als Argumente für den Indexoperator verwendet werden können (nützlich, wenn Sie z. B. eine Compiletime-Map haben).

Live-Beispiel .

namespace  variadic_toolbox
{
    template<unsigned  count, 
        template<unsigned...> class  meta_functor, unsigned...  indices>
    struct  apply_range
    {
        typedef  typename apply_range<count-1, meta_functor, count-1, indices...>::result  result;
    };

    template<template<unsigned...> class  meta_functor, unsigned...  indices>
    struct  apply_range<0, meta_functor, indices...>
    {
        typedef  typename meta_functor<indices...>::result  result;
    };
}

namespace  compile_time
{
    template<char...  str>
    struct  string
    {
        static  constexpr  const char  chars[sizeof...(str)+1] = {str..., '\0'};
    };

    template<char...  str>
    constexpr  const char  string<str...>::chars[sizeof...(str)+1];

    template<typename  lambda_str_type>
    struct  string_builder
    {
        template<unsigned... indices>
        struct  produce
        {
            typedef  string<lambda_str_type{}.chars[indices]...>  result;
        };
    };
}

#define  CSTRING(string_literal)                                                        \
    []{                                                                                 \
        struct  constexpr_string_type { const char * chars = string_literal; };         \
        return  variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
            compile_time::string_builder<constexpr_string_type>::produce>::result{};    \
    }()


#define  FIXED_CSTRING(string_literal)                                                        \
    ([]{                                                                                 \
        struct  constexpr_string_type { const char * chars = string_literal; };         \
        return  typename variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
            compile_time::string_builder<constexpr_string_type>::template produce>::result{};    \
    }())    

struct A {

    auto test() {
        return FIXED_CSTRING("blah"); // works
        // return CSTRING("blah"); // works too
    }

    template<typename X>
    auto operator[](X) {
        return 42;
    }
};

template<typename T>
struct B {

    auto test() {       
       // return CSTRING("blah");// does not compile
       return FIXED_CSTRING("blah"); // works
    }
};

int main() {
    A a;
    //return a[CSTRING("blah")]; // fails with error: two consecutive ' [ ' shall only introduce an attribute before ' [ ' token
    return a[FIXED_CSTRING("blah")];
}
florestan
quelle
0

Meine eigene Implementierung basiert auf dem Ansatz der Boost.HanaZeichenfolge (Vorlagenklasse mit variadischen Zeichen), verwendet jedoch nur den C++11Standard und die constexprFunktionen mit strikter Überprüfung der Kompilierbarkeit (wäre ein Kompilierungszeitfehler, wenn kein Kompilierungszeitausdruck). Kann aus der üblichen rohen C-Zeichenfolge anstelle von Phantasie {'a', 'b', 'c' }(über ein Makro) erstellt werden.

Implementierung: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/tmpl_string.hpp

Tests: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/src/tests/unit/test_tmpl_string.cpp

Anwendungsbeispiele:

const auto s0    = TACKLE_TMPL_STRING(0, "012");            // "012"
const char c1_s0 = UTILITY_CONSTEXPR_GET(s0, 1);            // '1'

const auto s1    = TACKLE_TMPL_STRING(0, "__012", 2);       // "012"
const char c1_s1 = UTILITY_CONSTEXPR_GET(s1, 1);            // '1'

const auto s2    = TACKLE_TMPL_STRING(0, "__012__", 2, 3);  // "012"
const char c1_s2 = UTILITY_CONSTEXPR_GET(s2, 1);            // '1'

// TACKLE_TMPL_STRING(0, "012") and TACKLE_TMPL_STRING(1, "012")
//   - semantically having different addresses.
//   So id can be used to generate new static array class field to store
//   a string bytes at different address.

// Can be overloaded in functions with another type to express the compiletimeness between functions:

template <uint64_t id, typename CharT, CharT... tchars>
const overload_resolution_1 & test_overload_resolution(const tackle::tmpl_basic_string<id, CharT, tchars...> &);
template <typename CharT>
const overload_resolution_2 & test_overload_resolution(const tackle::constexpr_basic_string<CharT> &);

// , where `constexpr_basic_string` is another approach which loses
//   the compiletimeness between function signature and body border,
//   because even in a `constexpr` function the compile time argument
//   looses the compiletimeness nature and becomes a runtime one.

Die Details zu einer constexprFunktion kompilieren den Zeitrahmen: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

Weitere Verwendungsdetails finden Sie in den Tests.

Das gesamte Projekt ist derzeit experimentell.

Andry
quelle
0

In C ++ 17 mit einer Hilfsmakrofunktion ist es einfach, Zeichenfolgen für die Kompilierungszeit zu erstellen:

template <char... Cs>
struct ConstexprString
{
    static constexpr int size = sizeof...( Cs );
    static constexpr char buffer[size] = { Cs... };
};

template <char... C1, char... C2>
constexpr bool operator==( const ConstexprString<C1...>& lhs, const ConstexprString<C2...>& rhs )
{
    if( lhs.size != rhs.size )
        return false;

    return std::is_same_v<std::integer_sequence<char, C1...>, std::integer_sequence<char, C2...>>;
}




template <typename F, std::size_t... Is>
constexpr auto ConstexprStringBuilder( F f, std::index_sequence<Is...> )
{
    return ConstexprString<f( Is )...>{};
}

#define CONSTEXPR_STRING( x )                                              \
  ConstexprStringBuilder( []( std::size_t i ) constexpr { return x[i]; },  \
                 std::make_index_sequence<sizeof(x)>{} )

Und dies ist ein Anwendungsbeispiel:

auto n = CONSTEXPR_STRING( "ab" );
auto m = CONSTEXPR_STRING( "ab" );


static_assert(n == m);
zurrutik
quelle