Dichiarare convenientemente stringhe di compilazione in C ++


137

Essere in grado di creare e manipolare stringhe durante la compilazione in C ++ ha diverse utili applicazioni. Sebbene sia possibile creare stringhe in fase di compilazione in C ++, il processo è molto complicato, poiché la stringa deve essere dichiarata come una sequenza variadica di caratteri, ad es.

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

Operazioni come la concatenazione di stringhe, l'estrazione di sottostringhe e molte altre, possono essere facilmente implementate come operazioni su sequenze di caratteri. È possibile dichiarare le stringhe in fase di compilazione in modo più conveniente? In caso contrario, esiste una proposta in corso che consenta una comoda dichiarazione delle stringhe in fase di compilazione?

Perché gli approcci esistenti falliscono

Idealmente, vorremmo essere in grado di dichiarare le stringhe in fase di compilazione come segue:

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

oppure, usando valori letterali definiti dall'utente,

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

dove decltype(str2)avrebbe un constexprcostruttore. È possibile implementare una versione più disordinata dell'approccio 1, sfruttando il fatto che è possibile effettuare le seguenti operazioni:

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

Tuttavia, l'array dovrebbe avere un collegamento esterno, quindi per far funzionare l'approccio 1, dovremmo scrivere qualcosa del genere:

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

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

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

Inutile dire che questo è molto scomodo. L'approccio 2 in realtà non è possibile implementare. Se dovessimo dichiarare un ( constexpr) letterale operatore, come potremmo specificare il tipo restituito? Poiché è necessario che l'operatore restituisca una sequenza variadica di caratteri, è necessario utilizzare il const char*parametro per specificare il tipo restituito:

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

Ciò provoca un errore di compilazione, perché snon è un constexpr. Cercare di aggirare il problema nel modo seguente non aiuta molto.

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

Lo standard impone che questo specifico modulo di operatore letterale sia riservato ai tipi interi e in virgola mobile. Mentre 123_sfunzionerebbe, abc_sno. Cosa succede se abbandoniamo del tutto i letterali definiti dall'utente e utilizziamo semplicemente una constexprfunzione regolare ?

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

Come prima, incontriamo il problema che l'array, ora un parametro della constexprfunzione, non è più un constexprtipo.

Credo che dovrebbe essere possibile definire una macro del preprocessore C che accetta una stringa e la dimensione della stringa come argomenti e restituisce una sequenza composta dai caratteri nella stringa (utilizzo BOOST_PP_FOR, stringa, sottoscrizioni di array e simili). Tuttavia, non ho il tempo (o abbastanza interesse) per implementare tale macro =)


2
Boost ha una macro che definisce una stringa che può essere utilizzata come espressione costante. Bene, definisce una classe che ha un membro stringa. L'hai verificato?
Pubblicazione


1
Stack Overflow non è il luogo appropriato per chiedere se esiste una proposta per qualcosa. Il posto migliore per questo sarebbe il sito C ++ .
Nicol Bolas

1
Fondamentalmente, espandi i caratteri memorizzati nell'array / ptr in un pacchetto di parametri (come ha fatto Xeo). Sebbene non siano suddivisi in argomenti di modello non di tipo, è possibile utilizzarli all'interno di constexprfunzioni e inizializzare matrici (quindi, concat, substr ecc.).
dyp

1
@MareInfinitus In breve, le constexprstringhe possono essere analizzate durante il tempo di compilazione, in modo da poter prendere diversi percorsi di codice a seconda dei risultati. In sostanza, è possibile creare EDL in C ++; le applicazioni sono piuttosto illimitate.
void-pointer

Risposte:


127

Non ho visto nulla che corrisponda all'eleganza di Scott Schurrstr_const presentata al C ++ Now 2012 . constexprTuttavia richiede .

Ecco come puoi usarlo e cosa può fare:

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

Non fa molto più freddo del controllo dell'intervallo in fase di compilazione!

Sia l'uso che l'implementazione sono privi di macro. E non esiste un limite artificiale alla dimensione della stringa. Pubblicherei l'implementazione qui, ma sto rispettando il copyright implicito di Scott. L'implementazione è su una singola diapositiva della sua presentazione collegata sopra.


3
Le operazioni che creano nuove stringhe constexpr (come la concatenazione di stringhe e l'estrazione di sottostringhe) possono funzionare con questo approccio? Forse usando due classi constexpr-string (una basata su str_conste l'altra basata su sequence), questo potrebbe essere possibile. L'utente userebbe str_constper inizializzare la stringa, ma le successive operazioni che creano nuove stringhe restituirebbero sequenceoggetti.
void-pointer

5
Questo è un buon pezzo di codice. Tuttavia, questo approccio presenta ancora un difetto rispetto a una stringa dichiarata con una sequenza di caratteri come parametri del modello: uno str_const è un valore costante e non un tipo, impedendo così l'uso di molti modi di dire di metaprogrammazione.
Jean-Bernard Jansen,

1
@JBJansen, è possibile, senza funzioni hash, compilare una stringa in un tipo che può quindi essere utilizzato come parametro del modello. Ogni stringa diversa fornisce un tipo diverso. L'idea di base è trasformare la stringa in un pacchetto di caratteri template<char... cs>. In teoria, potresti costruire qualcosa che prende una stringa letterale e compila il contenuto in una funzione. Vedi la risposta di dyp. Una libreria dall'aspetto molto completo è metaparse . In sostanza, è possibile definire qualsiasi mapping da stringhe letterali a tipi e implementarlo con questo tipo di tecnologia.
Aaron McDaid,

1
Non condivido l'entusiasmo ... non funziona con le metafunzioni del modello - molto fastidiose a causa dello sciocco compromesso che le funzioni di constexpr devono essere richiamabili in fase di esecuzione - nessuna concatenazione vera, richiede la definizione di un array di caratteri (brutto nell'intestazione) - anche se questo è vero per la maggior parte delle soluzioni macroless grazie al summenzionato compromesso constexpr - e il controllo della portata non mi impressiona molto perché anche il low cost const char * ha questo. Ho arrotolato la mia stringa di pacchetto di parametri, che può anche essere fatta da un valore letterale (usando una metafunzione) al costo di una definizione di matrice.
Arne Vogel,

2
@ user975326: Ho appena esaminato la mia implementazione di questo e sembra che abbia aggiunto un constexpr operator==. Scusate. La presentazione di Scott dovrebbe iniziare su come farlo. È molto più semplice in C ++ 14 che in C ++ 11. Non mi preoccuperei nemmeno di provare in C ++ 11. Guarda gli ultimi constexprdiscorsi di Scott qui: youtube.com/user/CppCon
Howard Hinnant,

41

Credo che dovrebbe essere possibile definire una macro del preprocessore C che accetta una stringa e la dimensione della stringa come argomenti e restituisce una sequenza composta dai caratteri nella stringa (utilizzando BOOST_PP_FOR, stringificazione, sottoscrizioni di array e simili). Tuttavia, non ho il tempo (o abbastanza interesse) per implementare una tale macro

è possibile implementarlo senza fare affidamento su boost, usando macro molto semplici e alcune delle funzionalità di C ++ 11:

  1. lambdas variadic
  2. modelli
  3. espressioni costanti generalizzate
  4. inizializzatori di membri di dati non statici
  5. inizializzazione uniforme

(gli ultimi due non sono strettamente richiesti qui)

  1. dobbiamo essere in grado di creare un'istanza di un modello variadico con le indicazioni fornite dall'utente da 0 a N - uno strumento utile anche ad esempio per espandere la tupla nell'argomento della funzione del modello variadico (vedi domande: Come posso espandere una tupla negli argomenti della funzione del modello variadico?
    " scompattare "una tupla per chiamare un puntatore a funzione corrispondente )

    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. quindi definire un modello variadic chiamato stringa con parametro non tipo char:

    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. ora la parte più interessante - per passare letterali di caratteri nel modello di stringa:

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

una semplice dimostrazione di concatenazione mostra l'utilizzo:

    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


1
È così semplice che non riesco ancora a credere che funzioni. +1! Una cosa: non dovresti usare size_t invece di unsigned?
Kirbyfan64sos,

1
E che dire dell'utilizzo operator+invece di operator*? (str_hello + str_world)
Remy Lebeau,

Preferisco questa soluzione al popolare metodo str_const di Scott Schurr, poiché questo metodo garantisce che i dati sottostanti siano constexpr. Il metodo di Schurr mi permette di creare uno str_const in fase di esecuzione con una variabile stack char []; Non riesco a restituire in modo sicuro uno str_const da una funzione o passarlo a un altro thread.
Glenn,

Il link è morto ... qualcuno può ripubblicarlo? @Glenn?
einpoklum,

Dovresti aggiungere un paio extra di parentesi graffe attorno alla lambda nella tua CSTRINGmacro. Altrimenti non è possibile creare una CSTRINGchiamata interna a un []operatore, poiché il doppio [[è riservato agli attributi.
florestan,

21

Modifica: come ha sottolineato Howard Hinnant (e io in qualche modo nel mio commento all'OP), potresti non aver bisogno di un tipo con ogni singolo carattere della stringa come singolo argomento del modello. Se ne hai bisogno, c'è una soluzione senza macro di seguito.

C'è un trucco che ho trovato mentre cercavo di lavorare con le stringhe in fase di compilazione. Richiede di introdurre un altro tipo oltre alla "stringa modello", ma all'interno delle funzioni è possibile limitare l'ambito di questo tipo.

Non utilizza macro ma piuttosto alcune funzionalità di C ++ 11.

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

1
Ho appena trascorso il fine settimana in modo indipendente sviluppando un pezzo di codice simile e creando un sistema molto semplice per analizzare stringhe di tipo, ad es pair<int,pair<char,double>>. Ero orgoglioso di me stesso e ho scoperto questa risposta e la libreria metaparse oggi! Dovrei davvero cercare SO in modo più approfondito prima di iniziare progetti sciocchi come questo :-) Immagino che, in teoria, un compilatore completamente C ++ possa essere costruito da questo tipo di tecnologia. Qual è la cosa più folle che è stata costruita con questo?
Aaron McDaid,

Non lo so. Non ho mai usato queste tecniche in un progetto del mondo reale, quindi non ho seguito l'approccio. Anche se penso di ricordare una leggera variazione del trucco di tipo locale che era leggermente più conveniente .. forse una statica locale char[].
dyp,

Intendi my_str.print();invece str.print();?
mike,

Esiste una versione leggermente più corta di C ++ 14?
mike,

È un peccato che tu debba creare il provider (almeno in C ++ 11) - Mi piacerebbe davvero poter usare una stringa nella stessa istruzione: /
Alec Teal

10

Se non si desidera utilizzare la soluzione Boost, è possibile creare macro semplici che eseguiranno operazioni simili:

#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!")>;

L'unico problema è la dimensione fissa di 64 caratteri (più zero aggiuntivo). Ma può essere facilmente modificato in base alle tue esigenze.


Mi piace molto questa soluzione; è molto semplice e fa il lavoro elegantemente. È possibile modificare la macro in modo che non venga aggiunto nulla sizeof(str) > i(anziché aggiungere i 0,token extra )? È facile definire una trimmetafunzione che lo farà dopo che la macro è già stata chiamata, ma sarebbe bello se la macro stessa potesse essere modificata.
void-pointer

È impossibile perché il parser non capisce sizeof(str). È possibile aggiungere manualmente le dimensioni della stringa come MACRO_GET_STR(6, "Hello")ma ciò richiede il funzionamento delle macro Boost perché la scrittura manuale richiede 100 volte più codice (è necessario implementare cose semplici come 1+1).
Yankes

6

Credo che dovrebbe essere possibile definire una macro del preprocessore C che accetta una stringa e le dimensioni della stringa come argomenti e restituisce una sequenza composta dai caratteri nella stringa (utilizzando BOOST_PP_FOR, stringificazione, sottoscrizioni di array e simili)

C'è un articolo: Usare le stringhe nei metaprogrammi del modello C ++ di Abel Sinkovics e Dave Abrahams.

Ha qualche miglioramento rispetto alla tua idea di utilizzare macro + BOOST_PP_REPEAT - non richiede il passaggio di dimensioni esplicite alla macro. In breve, si basa sul limite superiore fisso per la dimensione della stringa e "protezione da sovraccarico della stringa":

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

più boost condizionale :: mpl :: push_back .


Ho cambiato la mia risposta accettata alla soluzione di Yankes, poiché risolve questo problema specifico e lo fa in modo elegante senza l'uso di constexpr o di un codice preprocessore complesso.

Se accetti zero finali, ciclo macro scritto a mano, 2x ripetizione di stringa in macro espansa e non hai Boost - allora sono d'accordo - è meglio. Tuttavia, con Boost sarebbero solo tre righe:

DIMOSTRAZIONE DAL VIVO

#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

Inizialmente ho cambiato la soluzione in Yankes, dato che ha fornito il primo esempio funzionante qui. A questo punto, ci sono molte buone idee in competizione. È stato un mio errore scegliere una risposta così presto. Al momento osserverò questa domanda come senza risposta e rimarrò fino a quando non avrò il tempo di provare le idee che tutti hanno pubblicato qui. Ci sono molte informazioni utili nelle risposte che le persone hanno dato qui ...
void-pointer

Sono d'accordo - ad esempio, mi piace l'esempio di Howard Hinnant.
Evgeny Panasyuk

5

A nessuno sembra piacere la mia altra risposta: - <. Quindi qui mostro come convertire uno str_const in un tipo reale:

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

Compila con clang ++ -stdlib = libc ++ -std = c ++ 14 (clang 3.7)


Funziona bene, ma non per msvc 2019, poiché si lamenta che str.size () non sia constexpr. Può essere risolto aggiungendo un secondo utilizzando ded.sing separatamente str.size (). Forse questo ha trattenuto alcuni voti ;-)
Zaccaria il

4

Ecco una soluzione succinta di C ++ 14 per la creazione di uno std :: tuple <char ...> per ogni stringa di compilazione in tempo passata.

#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");

Ed eccone uno per la creazione di un tipo di tempo di compilazione unico, ridotto dall'altro post macro.

#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");

È davvero un peccato che i letterali definiti dall'utente non possano ancora essere utilizzati per questo.


In realtà, possono utilizzare un'estensione supportata da GCC / Clang, ma aspetterò prima che questo venga aggiunto allo standard prima di pubblicarlo come risposta.
void-pointer

3

Un collega mi ha sfidato a concatenare le stringhe in memoria al momento della compilazione. Include anche l'istanza di singole stringhe in fase di compilazione. L'elenco completo dei codici è qui:

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

Sei sicuro che sia fatto al momento della compilazione? C'è stata una discussione al riguardo qualche tempo fa, e per me il risultato non è chiaro.
dyp,

La corsa objdump -t a.out |grep mynon trova nulla. Quando ho iniziato a digitare questo codice, ho continuato a sperimentare la rimozione constexprdalle funzioni e ho objdumpmostrato loro quando è constexprstato omesso. Sono fiducioso al 99,9% al momento della compilazione.
Átila Neves,

1
Se guardi disassembly ( -S), noterai che gcc (4.7.2) risolve davvero le constexprfunzioni in fase di compilazione. Tuttavia, le stringhe non sono assemblate in fase di compilazione. Piuttosto, (se lo interpreto correttamente) per ogni carattere di quelle stringhe "assemblate", c'è una propria movboperazione, che è probabilmente l'ottimizzazione che stavi cercando.
dyp

2
È vero. Ho provato di nuovo con gcc 4.9 e fa ancora la stessa cosa. Ho sempre pensato che questo fosse il compilatore che era stupido, ma solo ieri ho pensato di provare un compilatore diverso. Con clang, i mov byte per byte non ci sono affatto. Con gcc, -Os si sbarazza anche di loro, ma -O3 fa la stessa cosa.
Átila Neves,

2

basato sull'idea di Howard Hinnant , puoi creare una classe letterale che sommerà due letterali insieme.

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" 

da dove str_atviene?
mic_e

è qualcosa del genere:str_at<int I>(const char* a) { return a[i]; }
Yankes

2

Il tuo approccio n. 1 è quello corretto.

Tuttavia, l'array dovrebbe avere un collegamento esterno, quindi per far funzionare l'approccio 1, dovremmo scrivere qualcosa del genere: constexpr const char str [] = "Ciao, mondo!";

No, non corretto. Questo viene compilato con clang e gcc. Spero che sia standard c ++ 11, ma non sono un linguaggio laywer.

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

Quello che mi piacerebbe davvero per c ++ 17 sarebbe il seguente essere equivalente (per completare l'approccio n. 1)

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

Qualcosa di molto simile esiste già nello standard per i letterali definiti dall'utente basati su modelli, come menziona anche il puntatore vuoto, ma solo per le cifre. Fino ad allora un altro piccolo trucco è usare la modalità di modifica override + copia e incolla di

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

Se non ti dispiace la macro, allora funziona (leggermente modificato dalla risposta di Yankes):

#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!)>();

2

La soluzione di Kacey per la creazione di un tipo di compilazione univoco può essere utilizzata anche con C ++ 11:

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

Uso:

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

test(CSTR("Hello"));

2

Mentre giocavo con la mappa hana boost, mi sono imbattuto in questa discussione. Poiché nessuna delle risposte ha risolto il mio problema, ho trovato una soluzione diversa che desidero aggiungere qui in quanto potrebbe essere potenzialmente utile per gli altri.

Il mio problema era che quando si utilizzava la mappa boost di hana con stringhe di hana, il compilatore generava comunque del codice di runtime (vedi sotto). Il motivo era ovviamente che per interrogare la mappa in fase di compilazione doveva essere constexpr. Questo non è possibile in quanto la BOOST_HANA_STRINGmacro genera un lambda, che non può essere utilizzato nel constexprcontesto. D'altra parte, la mappa ha bisogno di stringhe con contenuti diversi per essere tipi diversi.

Dato che le soluzioni in questo thread utilizzano un lambda o non forniscono tipi diversi per contenuti diversi, ho trovato utile il seguente approccio. Inoltre evita la str<'a', 'b', 'c'>sintassi confusa .

L'idea di base è avere una versione di Scott Schurr ispirata str_constall'hash dei personaggi. Lo è c++14, ma c++11dovrebbe essere possibile con un'implementazione ricorsiva della crc32funzione (vedi qui ).

// 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 ))>

Uso:

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

Il codice assembler risultante con clang-cl5.0 è:

012A1370  mov         eax,2  
012A1375  ret  

0

Vorrei aggiungere due piccoli miglioramenti alla risposta di @ user1115339. Li ho menzionati nei commenti alla risposta, ma per comodità inserirò qui una soluzione di copia e incolla.

L'unica differenza è la FIXED_CSTRINGmacro, che consente di utilizzare le stringhe all'interno dei modelli di classe e come argomenti per l'operatore dell'indice (utile se si dispone, ad esempio, di una mappa compilata).

Esempio dal vivo .

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

0

La mia implementazione si basa sull'approccio dalla Boost.Hanastringa (classe template con caratteri variadici), ma utilizza solo lo C++11standard e le constexprfunzioni con un controllo rigoroso sulla completezza (sarebbe un errore di compilazione se non un'espressione di compilazione). Può essere costruito dalla solita stringa C grezza invece che dalla fantasia {'a', 'b', 'c' }(attraverso una macro).

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

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

Esempi di utilizzo:

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.

I dettagli su un constexprconfine temporale di compilazione di funzioni: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

Per altri dettagli sull'uso vedere i test.

L'intero progetto è attualmente sperimentale.


0

In C ++ 17 con una funzione macro helper è facile creare stringhe di tempo di compilazione:

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)>{} )

E questo è un esempio di utilizzo:

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


static_assert(n == m);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.