Déclarer facilement des chaînes de compilation en C ++


137

Être capable de créer et de manipuler des chaînes pendant la compilation en C ++ a plusieurs applications utiles. Bien qu'il soit possible de créer des chaînes de compilation en C ++, le processus est très lourd, car la chaîne doit être déclarée comme une séquence variadique de caractères, par exemple

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

Des opérations telles que la concaténation de chaînes, l'extraction de sous-chaînes et bien d'autres peuvent facilement être implémentées en tant qu'opérations sur des séquences de caractères. Est-il possible de déclarer des chaînes de compilation plus facilement? Sinon, y a-t-il une proposition dans les travaux qui permettrait une déclaration pratique des chaînes de compilation?

Pourquoi les approches existantes échouent

Idéalement, nous aimerions pouvoir déclarer des chaînes de compilation comme suit:

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

ou, en utilisant des littéraux définis par l'utilisateur,

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

decltype(str2)aurait un constexprconstructeur. Une version plus désordonnée de l'approche 1 est possible à mettre en œuvre, en tirant parti du fait que vous pouvez effectuer les opérations suivantes:

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

Cependant, le tableau devrait avoir un lien externe, donc pour que l'approche 1 fonctionne, nous devions écrire quelque chose comme ceci:

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

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

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

Inutile de dire que c'est très gênant. L'approche 2 n'est en fait pas possible à mettre en œuvre. Si nous devions déclarer un constexpropérateur littéral ( ), comment spécifierions-nous le type de retour? Puisque nous avons besoin de l'opérateur pour renvoyer une séquence variadique de caractères, nous aurions donc besoin d'utiliser le const char*paramètre pour spécifier le type de retour:

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

Cela entraîne une erreur de compilation, car ce sn'est pas un fichier constexpr. Essayer de contourner ce problème en procédant comme suit n'aide pas beaucoup.

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

La norme stipule que cette forme d'opérateur littéral spécifique est réservée aux types entiers et flottants. Cela 123_sfonctionnerait, mais abc_spas. Et si nous abandonnions complètement les littéraux définis par l'utilisateur et utilisions simplement une constexprfonction régulière ?

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

Comme auparavant, nous nous heurtons au problème que le tableau, maintenant un paramètre de la constexprfonction, n'est plus un constexprtype.

Je pense qu'il devrait être possible de définir une macro de préprocesseur C qui prend une chaîne et la taille de la chaîne comme arguments, et renvoie une séquence composée des caractères de la chaîne (utilisation BOOST_PP_FOR, stringification, indices de tableau, etc.). Cependant, je n'ai pas le temps (ou assez d'intérêt) pour implémenter une telle macro =)


2
Boost a une macro qui définit une chaîne qui peut être utilisée comme expression constante. Eh bien, il définit une classe qui a un membre chaîne. Avez-vous vérifié cela?
Pubby


1
Stack Overflow n'est pas l'endroit approprié pour demander s'il existe une proposition pour quelque chose. Le meilleur endroit pour cela serait le site C ++ .
Nicol Bolas

1
Fondamentalement, vous développez les caractères stockés dans le tableau / ptr dans un pack de paramètres (comme Xeo l'a fait). Bien qu'ils ne soient pas divisés en arguments de modèle non-type, vous pouvez les utiliser dans des constexprfonctions et initialiser des tableaux (par conséquent, concat, substr, etc.).
dyp

1
@MareInfinitus En bref, les constexprchaînes peuvent être analysées pendant la compilation, de sorte que vous pouvez prendre différents chemins de code en fonction des résultats. Essentiellement, vous pouvez créer des EDL dans C ++; les applications sont assez illimitées.
void-pointer

Réponses:


127

Je n'ai rien vu qui corresponde à l'élégance de Scott Schurr'sstr_const présenté à C ++ Now 2012 . Cela nécessite constexprcependant.

Voici comment vous pouvez l'utiliser et ce qu'il peut faire:

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

Cela ne devient pas beaucoup plus cool que la vérification de la plage de compilation!

Tant l'utilisation que l'implémentation sont exemptes de macros. Et il n'y a pas de limite artificielle sur la taille des cordes. Je publierais l'implémentation ici, mais je respecte le droit d'auteur implicite de Scott. La mise en œuvre est sur une seule diapositive de sa présentation liée à ci-dessus.


3
Les opérations qui créent de nouvelles chaînes constexpr (comme la concaténation de chaînes et l'extraction de sous-chaînes) peuvent-elles fonctionner avec cette approche? Peut-être en utilisant deux classes constexpr-string (l'une basée sur str_constet l'autre basée sur sequence), cela peut être possible. L'utilisateur l'utilisera str_constpour initialiser la chaîne, mais les opérations suivantes qui créent de nouvelles chaînes renverront des sequenceobjets.
void-pointer

5
C'est un bon morceau de code. Cependant, cette approche présente toujours un défaut par rapport à une chaîne déclarée avec une séquence de caractères comme paramètres de modèle: un str_const est une valeur constante, et non un type, empêchant ainsi l'utilisation de nombreux idiomes de métaprogrammation.
Jean-Bernard Jansen

1
@JBJansen, il est possible, sans fonctions de hachage, de compiler une chaîne vers un type qui peut ensuite être utilisé comme paramètre de modèle. Chaque chaîne différente donne un type différent. L'idée de base est de transformer la chaîne en un pack de caractères template<char... cs>. En théorie, vous pouvez créer quelque chose qui prend une chaîne littérale et compile le contenu en une fonction. Voir la réponse par dyp. Une bibliothèque très complète est métaparse . Essentiellement, vous pouvez définir n'importe quel mappage des chaînes littérales aux types et l'implémenter avec ce type de technologie.
Aaron McDaid

1
Je ne partage pas l'enthousiasme ... ne fonctionne pas avec les métafonctions de modèle - très ennuyeux à cause du compromis idiot que les fonctions constexpr doivent être appelables à l'exécution - pas de véritable concaténation, nécessite la définition d'un tableau de caractères (laid dans l'en-tête) - bien que cela est vrai pour la plupart des solutions macroless grâce au compromis constexpr mentionné ci-dessus - et la vérification de plage ne m'impressionne pas beaucoup car même le modeste constexpr const char * l'a. J'ai roulé ma propre chaîne de pack de paramètres, qui peut également être créée à partir d'un littéral (en utilisant une métafonction) au prix d'une définition de tableau.
Arne Vogel

2
@ user975326: Je viens de passer en revue ma mise en œuvre de ceci et il semble que j'ai ajouté un fichier constexpr operator==. Désolé. La présentation de Scott devrait vous aider à démarrer. C'est beaucoup plus facile en C ++ 14 qu'en C ++ 11. Je ne prendrais même pas la peine d'essayer en C ++ 11. Voir les dernières constexprdiscussions de Scott ici: youtube.com/user/CppCon
Howard Hinnant

41

Je pense qu'il devrait être possible de définir une macro de préprocesseur C qui prend une chaîne et la taille de la chaîne comme arguments, et retourne une séquence composée des caractères de la chaîne (en utilisant BOOST_PP_FOR, la stringification, les indices de tableau, etc.). Cependant, je n'ai pas le temps (ou assez d'intérêt) pour implémenter une telle macro

il est possible de l'implémenter sans compter sur boost, en utilisant une macro très simple et certaines fonctionnalités de C ++ 11:

  1. lambdas variadic
  2. modèles
  3. expressions constantes généralisées
  4. initialiseurs de membres de données non statiques
  5. initialisation uniforme

(les deux derniers ne sont pas strictement requis ici)

  1. nous devons être en mesure d'instancier un modèle variadique avec des indices fournis par l'utilisateur de 0 à N - un outil également utile par exemple pour développer un tuple dans l'argument de la fonction de modèle variadique (voir les questions: Comment puis-je développer un tuple dans les arguments de la fonction de modèle variadique?
    " décompresser "un tuple pour appeler un pointeur de fonction correspondant )

    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. puis définissez un modèle variadique appelé string avec un paramètre non de type 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. maintenant la partie la plus intéressante - pour passer des littéraux de caractères dans un modèle de chaîne:

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

une simple démonstration de concaténation montre l'utilisation:

    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
C'est si simple que je n'arrive toujours pas à croire que cela fonctionne. +1! Une chose: ne devriez-vous pas utiliser size_t au lieu de unsigned?
kirbyfan64sos

1
Et qu'en est-il de l'utilisation operator+au lieu de operator*? (str_hello + str_world)
Remy Lebeau

Je préfère cette solution à la méthode populaire str_const de Scott Schurr, car cette méthode garantit que les données sous-jacentes sont constexpr. La méthode de Schurr me permet de créer un str_const à l'exécution avec une variable de pile char []; Je ne peux pas renvoyer en toute sécurité un str_const d'une fonction ou le passer à un autre thread.
Glenn

Le lien est mort ... quelqu'un peut-il le republier? @Glenn?
einpoklum

Vous devez ajouter une paire d'accolades supplémentaires autour du lambda dans votre CSTRINGmacro. Sinon, vous ne pouvez pas créer un CSTRINGappel interne à un []opérateur, car les doubles [[sont réservés aux attributs.
florestan

21

Edit: comme Howard Hinnant (et moi-même dans mon commentaire à l'OP) l'a souligné, vous n'aurez peut-être pas besoin d'un type avec chaque caractère de la chaîne comme argument de modèle unique. Si vous en avez besoin, il existe une solution sans macro ci-dessous.

J'ai trouvé une astuce en essayant de travailler avec des chaînes au moment de la compilation. Il nécessite d'introduire un autre type en plus de la "chaîne de modèle", mais dans les fonctions, vous pouvez limiter la portée de ce type.

Il n'utilise pas de macros mais plutôt certaines fonctionnalités 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
Je viens de passer le week-end à développer indépendamment un morceau de code similaire et à créer un système très basique pour analyser les chaînes de caractères, par exemple pair<int,pair<char,double>>. J'étais fier de moi et j'ai découvert cette réponse, ainsi que la bibliothèque métaparse aujourd'hui! Je devrais vraiment chercher SO plus en profondeur avant de démarrer des projets stupides comme celui-ci :-) Je suppose qu'en théorie, un compilateur entièrement C ++ pourrait être construit à partir de ce type de technologie. Quelle est la chose la plus folle qui ait été construite avec ça?
Aaron McDaid

Je ne sais pas. Je n'ai jamais vraiment utilisé ces techniques dans un projet réel, donc je n'ai pas poursuivi l'approche. Bien que je pense que je me souviens d'une légère variation de l'astuce de type local qui était légèrement plus pratique .. peut-être une statique locale char[].
dyp le

Voulez-vous dire my_str.print();au lieu de str.print();?
mike

Existe-t-il une version C ++ 14 légèrement plus courte?
mike le

C'est dommage que vous deviez créer le fournisseur (au moins en C ++ 11) - j'aimerais vraiment pouvoir utiliser une chaîne dans la même déclaration: /
Alec Teal

10

Si vous ne souhaitez pas utiliser la solution Boost, vous pouvez créer des macros simples qui feront quelque chose de similaire:

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

Le seul problème est la taille fixe de 64 caractères (plus zéro supplémentaire). Mais il peut facilement être modifié en fonction de vos besoins.


J'aime beaucoup cette solution; c'est très simple et fait le travail avec élégance. Est-il possible de modifier la macro pour que rien ne soit ajouté sizeof(str) > i(au lieu d'ajouter les 0,jetons supplémentaires )? Il est facile de définir une trimmétafonction qui le fera après que la macro a déjà été appelée, mais ce serait bien si la macro elle-même pouvait être modifiée.
void-pointer

Est impossible parce que l'analyseur ne comprend pas sizeof(str). Il est possible d'ajouter manuellement une taille de chaîne comme MACRO_GET_STR(6, "Hello")mais cela nécessite des macros Boost pour fonctionner car l'écriture manuelle nécessite 100 fois plus de code (vous avez besoin d'implémentations simples comme 1+1).
Yankes

6

Je pense qu'il devrait être possible de définir une macro de préprocesseur C qui prend une chaîne et la taille de la chaîne comme arguments, et retourne une séquence composée des caractères de la chaîne (en utilisant BOOST_PP_FOR, la stringification, les indices de tableau, etc.)

Il y a un article: Utilisation de chaînes dans les métaprogrammes de modèle C ++ par Abel Sinkovics et Dave Abrahams.

Il a une certaine amélioration par rapport à votre idée d'utiliser la macro + BOOST_PP_REPEAT - il ne nécessite pas de passer une taille explicite à la macro. En bref, il est basé sur une limite supérieure fixe pour la taille de la chaîne et la "protection contre le dépassement de chaîne":

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

plus boost conditionnel :: mpl :: push_back .


J'ai changé ma réponse acceptée à la solution de Yankes, car elle résout ce problème spécifique, et le fait avec élégance sans l'utilisation de constexpr ou de code préprocesseur complexe.

Si vous acceptez les zéros de fin, les boucles de macro manuscrites, la répétition 2x de la chaîne dans la macro développée et que vous n'avez pas Boost - alors je suis d'accord - c'est mieux. Cependant, avec Boost, ce ne serait que trois lignes:

DÉMO EN DIRECT

#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

J'ai d'abord changé la solution en Yankes, puisqu'il a fourni le premier exemple de travail ici. À ce stade, il y a beaucoup de bonnes idées concurrentes. Ce fut mon erreur de choisir une réponse si tôt. Je vais actuellement remarquer cette question comme sans réponse, et attendre jusqu'à ce que j'aie le temps d'essayer les idées que tout le monde a publiées ici. Il y a beaucoup d'informations utiles dans les réponses que les gens ont données ici ...
void-pointer

Je suis d'accord - par exemple, j'aime l'exemple de Howard Hinnant.
Evgeny Panasyuk

5

Personne ne semble aimer mon autre réponse: - <. Donc, ici, je montre comment convertir un str_const en un type réel:

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

Compile avec clang ++ -stdlib = libc ++ -std = c ++ 14 (clang 3.7)


Fonctionne bien, mais pas pour msvc 2019, car il se plaint du fait que str.size () n'est pas constexpr. Peut être corrigé en ajoutant un 2ème en déduisant séparément str.size (). Peut-être que cela a retenu quelques votes positifs ;-)
Zacharias

4

Un collègue m'a mis au défi de concaténer des chaînes en mémoire au moment de la compilation. Il inclut également l'instanciation de chaînes individuelles au moment de la compilation. La liste complète des codes est ici:

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

Vous êtes sûr que cela est fait au moment de la compilation? Il y a eu une discussion à ce sujet il y a quelque temps, et pour moi, le résultat n'est pas clair.
dyp le

1
Courir objdump -t a.out |grep myne trouve rien. Quand j'ai commencé à taper ce code, j'ai continué à expérimenter la suppression constexprdes fonctions et objdumpleur ai montré quand constexprétait omis. Je suis sûr à 99,9% que cela se produit au moment de la compilation.
Átila Neves

1
Si vous regardez le désassemblage ( -S), vous remarquerez que gcc (4.7.2) résout effectivement les constexprfonctions au moment de la compilation. Pourtant, les chaînes ne sont pas assemblées au moment de la compilation. Au contraire, (si je l'interprète correctement) pour chaque caractère de ces chaînes "assemblées", il y a une propre movbopération, qui est sans doute l'optimisation que vous recherchiez.
dyp

2
C'est vrai. J'ai réessayé avec gcc 4.9 et il fait toujours la même chose. J'ai toujours pensé que c'était le compilateur qui était stupide. Ce n'est qu'hier que j'ai pensé essayer un autre compilateur. Avec clang, les mouvements par octets ne sont pas du tout là. Avec gcc, -Os s'en débarrasse aussi, mais -O3 fait la même chose.
Átila Neves

4

Voici une solution succincte en C ++ 14 pour créer un std :: tuple <char ...> pour chaque chaîne passée au moment de la compilation.

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

Et en voici un pour créer un type de compilation unique, réduit à partir de l'autre publication de 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");

C'est vraiment dommage que les littéraux définis par l'utilisateur ne puissent pas encore être utilisés pour cela.


En fait, ils peuvent utiliser une extension prise en charge par GCC / Clang, mais je vais attendre avant que cela soit ajouté à la norme avant de la publier comme réponse.
void-pointer

2

basé sur l'idée de Howard Hinnant, vous pouvez créer une classe littérale qui ajoutera deux littéraux ensemble.

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" 

d'où str_atvient-il?
mic_e

c'est str_at<int I>(const char* a) { return a[i]; }
quelque chose

2

Votre approche n ° 1 est la bonne.

Cependant, le tableau aurait besoin d'avoir une liaison externe, donc pour que l'approche 1 fonctionne, nous devrions écrire quelque chose comme ceci: constexpr const char str [] = "Hello, world!";

Non, pas correct. Cela compile avec clang et gcc. J'espère que sa norme c ++ 11, mais je ne suis pas un prof de langage.

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

Ce que j'aimerais vraiment pour c ++ 17 serait le suivant pour être équivalent (pour compléter l'approche n ° 1)

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

Quelque chose de très similaire existe déjà dans la norme pour les littéraux définis par l'utilisateur, comme le mentionne également void-pointer, mais uniquement pour les chiffres. Jusque-là, une autre petite astuce consiste à utiliser le mode d'édition de remplacement + copier et coller de

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

Si la macro ne vous dérange pas, cela fonctionne (légèrement modifié par rapport à la réponse des 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 solution de kacey pour créer un type de compilation unique peut, avec des modifications mineures, également être utilisée avec 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(); \
  }()

Utilisation:

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

test(CSTR("Hello"));

2

En jouant avec la carte boost hana, je suis tombé sur ce fil. Comme aucune des réponses n'a résolu mon problème, j'ai trouvé une solution différente que je veux ajouter ici car elle pourrait être potentiellement utile pour d'autres.

Mon problème était que lors de l'utilisation de la carte boost hana avec des chaînes hana, le compilateur générait toujours du code d'exécution (voir ci-dessous). La raison était évidemment que pour interroger la carte au moment de la compilation, cela devait l'être constexpr. Ce n'est pas possible car la BOOST_HANA_STRINGmacro génère un lambda, qui ne peut pas être utilisé en constexprcontexte. D'autre part, la carte a besoin de chaînes avec un contenu différent pour être de types différents.

Comme les solutions de ce fil utilisent un lambda ou ne fournissent pas différents types pour différents contenus, j'ai trouvé l'approche suivante utile. Cela évite également la str<'a', 'b', 'c'>syntaxe hacky .

L'idée de base est d'avoir une version du str_constmodèle de Scott Schurr sur le hachage des personnages. C'est c++14, mais c++11devrait être possible avec une implémentation récursive de la crc32fonction (voir ici ).

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

Usage:

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

Le code assembleur résultant avec clang-cl5.0 est:

012A1370  mov         eax,2  
012A1375  ret  

0

J'aimerais ajouter deux très petites améliorations à la réponse de @ user1115339. Je les ai mentionnés dans les commentaires de la réponse, mais pour plus de commodité, je vais mettre une solution de copier-coller ici.

La seule différence est la FIXED_CSTRINGmacro, qui permet d'utiliser les chaînes dans les modèles de classe et comme arguments de l'opérateur d'index (utile si vous avez par exemple une carte de compilation).

Exemple en direct .

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

Ma propre implémentation est basée sur l'approche de la Boost.Hanachaîne (classe de modèle avec des caractères variadiques), mais n'utilise que le C++11standard et des constexprfonctions avec un contrôle strict de la compatibilité (ce serait une erreur de compilation si ce n'est une expression de temps de compilation). Peut être construit à partir de la chaîne C brute habituelle au lieu de fantaisie {'a', 'b', 'c' }(via une macro).

Mise en œuvre: 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

Exemples d'utilisation:

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.

Les détails sur une constexprlimite de temps de compilation de fonction: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

Pour d'autres détails d'utilisation, consultez les tests.

L'ensemble du projet est actuellement expérimental.


0

En C ++ 17 avec une fonction de macro d'assistance, il est facile de créer des chaînes de compilation:

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

Et voici un exemple d'utilisation:

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


static_assert(n == m);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.