Удобное объявление строк времени компиляции в C++

Возможность создавать и манипулировать строками во время компиляции в C++ имеет несколько полезных приложений. Хотя в C++ можно создавать строки времени компиляции, этот процесс очень громоздок, так как строка должна быть объявлена ​​как последовательность символов, например

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

Такие операции, как конкатенация строк, извлечение подстрок и многие другие, могут быть легко реализованы как операции над последовательностями символов. Можно ли более удобно объявить строки времени компиляции? Если нет, есть ли в работах предложение, которое позволило бы удобно объявить строки времени компиляции?

Почему существующие подходы терпят неудачу

В идеале мы хотели бы иметь возможность объявить строки времени компиляции следующим образом:

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

или, используя пользовательские литералы,

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

где decltype(str2) будет иметь constexpr конструктор. Более сложную версию подхода 1 можно реализовать, воспользовавшись тем, что вы можете сделать следующее:

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

Однако массив должен иметь внешнюю связь, поэтому для того, чтобы подход 1 работал, нам нужно написать что-то вроде этого:

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

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

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

Излишне говорить, что это очень неудобно. Подход 2 фактически невозможно реализовать. Если бы мы должны были объявить (constexpr) оператор литерала, то как бы мы указали тип возвращаемого значения? Так как нам нужен оператор для возврата переменной последовательности символов, поэтому нам нужно использовать const char* Параметр для указания типа возвращаемого значения:

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

Это приводит к ошибке компиляции, потому что s это не constexpr, Попытка обойти это, делая следующее, не очень помогает.

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

Стандарт диктует, что эта конкретная форма литерального оператора зарезервирована для целочисленных типов и типов с плавающей точкой. В то время как 123_s должно сработать, abc_s не будет. Что если мы вообще отбросим пользовательские литералы и просто используем обычные constexpr функционировать?

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

Как и прежде, мы сталкиваемся с проблемой, что массив, теперь параметр constexpr функция, сама по себе больше не constexpr тип.

Я считаю, что должно быть возможно определить макрос препроцессора C, который принимает строку и размер строки в качестве аргументов и возвращает последовательность, состоящую из символов в строке (используя BOOST_PP_FOR, stringification, индексы массива и тому подобное). Однако у меня нет времени (или достаточного интереса) для реализации такого макроса =)

21 ответ

Решение

Я не видел ничего подобного элегантности Скотта Шурраstr_const представленный на C++ Now 2012. Это требует constexpr хоть.

Вот как вы можете его использовать и что он может делать:

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

Это не становится намного круче, чем проверка диапазона времени компиляции!

И использование, и реализация свободны от макросов. И нет никаких искусственных ограничений на размер строки. Я бы опубликовал реализацию здесь, но я уважаю скрытое авторское право Скотта. Реализация находится на одном слайде его презентации, связанной с выше.

Я полагаю, что должна быть возможность определить макрос препроцессора C, который принимает строку и размер строки в качестве аргументов и возвращает последовательность, состоящую из символов в строке (используя BOOST_PP_FOR, stringification, индексы массива и тому подобное). Тем не менее, у меня нет времени (или достаточно интереса), чтобы реализовать такой макрос

это можно реализовать, не полагаясь на повышение, используя очень простой макрос и некоторые функции C++11:

  1. лямбда-вариад
  2. шаблоны
  3. обобщенные константные выражения
  4. инициализаторы нестатических элементов данных
  5. равномерная инициализация

(последние два здесь не обязательны)

  1. нам нужно иметь возможность создавать экземпляр шаблона переменной с указанными пользователем показателями от 0 до N - инструмент, который также полезен, например, для расширения кортежа в аргумент функции шаблона переменной (см. вопросы: Как расширить кортеж в аргументы функции шаблона переменной?
    "распаковка" кортежа для вызова соответствующего указателя на функцию)

    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. затем определите шаблон переменной с именем string с нетиповым параметром 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. Теперь самое интересное - передать символьные литералы в шаблон строки:

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

простая демонстрация конкатенации показывает использование:

    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

Редактировать: как отметил Говард Хиннант (и я в своем комментарии к ОП), вам может не понадобиться тип с каждым отдельным символом строки в качестве одного аргумента шаблона. Если вам это нужно, ниже приведено решение без макросов.

Есть одна хитрость, которую я нашел, пытаясь работать со строками во время компиляции. Требуется ввести другой тип помимо "строки шаблона", но внутри функций вы можете ограничить область действия этого типа.

Он не использует макросы, а скорее некоторые функции 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();
}

Если вы не хотите использовать Boost-решение, вы можете создать простой макрос, который будет выполнять аналогичные действия:

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

Единственная проблема - фиксированный размер 64 символа (плюс дополнительный ноль). Но это может быть легко изменено в зависимости от ваших потребностей.

Вот краткое решение C++14 для создания std::tuple для каждой передаваемой строки во время компиляции.

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

И вот один из них для создания уникального типа во время компиляции, обрезанного по сравнению с другим постом макроса.

#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, который принимает строку и размер строки в качестве аргументов и возвращает последовательность, состоящую из символов в строке (используя BOOST_PP_FOR, stringification, индексы массива и тому подобное)

Есть статья: Использование строк в шаблонных метапрограммах C++ Абеля Синковича и Дейва Абрахамса.

В нем есть некоторые улучшения по сравнению с вашей идеей использования макроса + BOOST_PP_REPEAT - он не требует передачи явного размера в макрос. Короче говоря, он основан на фиксированном верхнем пределе размера строки и "защите от переполнения строки":

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

плюс условное повышение:: mpl:: push_back.


Я изменил свой принятый ответ на решение Янкиса, поскольку оно решает эту конкретную проблему и делает это элегантно без использования constexpr или сложного кода препроцессора.

Если вы принимаете конечные нули, рукописные циклические макросы, 2- кратное повторение строки в расширенном макросе и не имеете Boost - тогда я согласен - это лучше. Хотя с Boost это будет всего три строчки:

LIVE DEMO

#include <boost/preprocessor/repetition/repeat.hpp>
#define GET_STR_AUX(_, i, str) (sizeof(str) > (i) ? str[(i)] : 0),
#define GET_STR(str) BOOST_PP_REPEAT(64,GET_STR_AUX,str) 0

Никто не любит мой другой ответ:-<. Итак, здесь я покажу, как преобразовать str_const в реальный тип:

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

Компилируется с помощью clang++ -stdlib=libC++ -std= C++14 (clang 3.7)

Коллега попросил меня объединить строки в памяти во время компиляции. Он также включает в себя создание отдельных строк во время компиляции. Полный список кодов здесь:

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

Ваш подход № 1 является правильным.

Однако массив должен иметь внешнюю связь, поэтому для того, чтобы подход 1 работал, нам нужно написать что-то вроде этого: constexpr const char str[] = "Hello, world!";

Нет, не правильно. Это компилируется с Clang и GCC. Я надеюсь, что это стандарт C++11, но я не являюсь языковым специалистом.

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

То, что я действительно хотел бы для C++17, было бы следующим, чтобы быть эквивалентным (для завершения подхода #1)

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

Нечто очень похожее уже существует в стандарте для шаблонных пользовательских литералов, поскольку void-pointer также упоминает, но только для цифр. До этого еще один маленький трюк заключается в использовании режима редактирования переопределения + копирование и вставка

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

Если вы не возражаете против макроса, то это работает (слегка измененный ответ от Янки):

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

Играя с буст-картой хана, я наткнулся на эту тему. Поскольку ни один из ответов не решил мою проблему, я нашел другое решение, которое хочу добавить сюда, поскольку оно может быть потенциально полезным для других.

Моя проблема заключалась в том, что при использовании карты расширенного хана со строками ханы компилятор все еще генерировал некоторый код времени выполнения (см. Ниже). Причина была, очевидно, в том, что для запроса карты во время компиляции она должна быть constexpr, Это невозможно, так как BOOST_HANA_STRING макрос генерирует лямбду, которую нельзя использовать в constexpr контекст. С другой стороны, карте нужны строки с разным содержанием, чтобы иметь разные типы.

Поскольку решения в этой теме либо используют лямбду, либо не предоставляют разные типы для разного содержимого, я нашел следующий подход полезным. Также избегает хаки str<'a', 'b', 'c'> синтаксис.

Основная идея заключается в том, чтобы иметь версию Скотта Шурра str_const на основе хэша символов. это c++14, но c++11 должно быть возможно с рекурсивной реализацией crc32 функция (см. здесь).

// 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://stackru.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 ))>

Использование:

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

Результирующий ассемблерный код с clang-cl 5.0 это:

012A1370  mov         eax,2  
012A1375  ret  

Решение kacey для создания уникального типа во время компиляции может, с небольшими изменениями, также использоваться с 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(); \
  }()

Использование:

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

test(CSTR("Hello"));

Основываясь на идее Говарда Хиннанта, вы можете создать литеральный класс, который сложит два литерала вместе.

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" 

В C++17 с помощью вспомогательной макрос-функции легко создавать строки времени компиляции:

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

А это пример использования:

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


static_assert(n == m);

Решение С++17

Я бы рекомендовал вам использовать собственный литеральный оператор

      template <char... chars>
using text = std::integer_sequence<char, chars...>;

template <typename T, T... chars>
constexpr text<chars...> operator""_text() { return { }; }

Что позволит вам получить от"some_text"_textэквивалентstd::integer_sequence<'s','o','m','e','_','t','e','x','t'>А затем создайте шаблон, в котором вы будете проверять и использовать этот тип.

      template <typename>
struct Field;

template <char... chars>
struct Field<text<chars...>> {
    static constexpr std::string_view Name()
    {
        constexpr const char data[] = {chars..., '\0'};
        return data;
    }
};

Наконец, вы сможете использовать его следующим образом:

      Field<decltype("field_name"_text)> object;
static_assert(object.Name() == std::string_view("field_name"));

Решение С++20

Вам понадобится специальный класс для вашего текста, он будет хранить ваш текст в памяти.

      template <size_t N>
struct StringLiteral {
    constexpr StringLiteral(const char (&_str)[N])
     : value_(std::to_array(_str))
    {}

    constexpr std::string_view Name() const {
        return std::string_view(value_.data(), N - 1);
    }

    const std::array<char, N> value_;
};

А в C++20 вы, наконец, можете использовать пользовательские классы в качестве аргумента шаблона и писать:

      template <StringLiteral name__>
struct Field {
  constexpr std::string_view Name() const {
      return name__.Name();
  }
};

Создать такой объект будет очень просто:

      Field<"field_name"> object;
static_assert(object.Name() == std::string_view("field_name"));

Моя собственная реализация основана на подходе из Boost.Hana строка (класс шаблона с вариативными символами), но использует только C++11 стандарт и constexprфункции со строгой проверкой времени компиляции (было бы ошибкой времени компиляции, если бы не выражением времени компиляции). Может быть построен из обычной сырой строки C вместо причудливой{'a', 'b', 'c' } (через макрос).

Реализация:https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/tmpl_string.hpp

Тесты:https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/src/tests/unit/test_tmpl_string.cpp

Примеры использования:

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.

Подробности о constexprграница времени компиляции функции: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html

Для других деталей использования смотрите тесты.

В настоящее время весь проект является экспериментальным.

Были сделаны некоторые улучшения:

      #pragma once
// 姜安富 2023-6-17
// 编译期字符串拼接
// 使用方式:
// constexpr auto demo_1 = constr::Str("123");
// constexpr auto demo_2 = constr::Str({ '4', '5', '6', 0 }); // 这种方式定义字符串时,最后要多附加一个0
// constexpr auto demo_sum = demo_1 + demo_2;

namespace constr 
{
// 创建整数序列
// 将整数转换为整数序列,如将 10 转换为 0,1,2,3,4,5,6,7,8,9 
// 使用方式大致为:
// gen_seq<10>::type;
// 实现C++14的make_integer_sequence功能为了在C++11标准下使用

template<class T> using Invoke = typename T::type;

template<unsigned...> struct seq { using type = seq; };

template<class S1, class S2> struct concat;

template<unsigned... I1, unsigned... I2>
struct concat<seq<I1...>, seq<I2...>>
    : seq<I1..., (sizeof...(I1) + I2)...> 
{};

template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;

template<unsigned N> struct gen_seq;
template<unsigned N> using GenSeq = Invoke<gen_seq<N>>;

template<unsigned N>
struct gen_seq : Concat<GenSeq<N / 2>, GenSeq<N - N / 2>> {};

template<> struct gen_seq<0> : seq<> {};
template<> struct gen_seq<1> : seq<0> {};

}

namespace constr 
{

// 编译期字符串
template<int Size>
struct String
{
    char str[Size + 1]; // 常字符串的数据
    int size = Size; // 字符串数据长度,不包括最后的0字符

    // 定义字符类型,让 Char<0>、Char<1>、Char<2>、Char<3>这样的类型都定义为char类型
    template<char> 
    using Char = char;

    // 构造函数1,为了使用 "abc"的形式来初始化str,需要使用整数序列
    template <int... Index>
    constexpr String(const char* str, seq<Index...>) :str{ str[Index]..., 0 }
    {}
    // 构造函数2,通过传入常字符串来构造对象,使用C++11提供的委托构造函数语法调用构造函数1
    constexpr String(const char* str) : String(str, gen_seq<Size>())
    {}

    // 构造函数3,通过传入多个字符来拼接字符串
    template <int... Index>
    constexpr String(seq<Index...>, Char<Index>... str) : str{ str..., 0 }
    {}

    // 构造函数4,拷贝构造函数
    template <int... Index>
    constexpr String(const String<Size>& other) : str{ other.str[Index]..., 0 }
    {}

    // 构造函数5,用一个单字符初始化
    constexpr String(char c) : str{ c , 0}
    {}

    // 构造函数6,初始化空编译期字符串
    constexpr String() : str{ 0 }
    {}

};

// 辅助字符串相加
struct Assist
{   
    template <int... AIndex,int... BIndex>
    constexpr static String<sizeof...(AIndex) + sizeof...(BIndex)> Add(const String<sizeof...(AIndex)>& str1, const String<sizeof...(BIndex)>& str2, seq<AIndex...>, seq<BIndex...>)
    {
        // 调用constr::String的构造函数3来实现相加
        return String<sizeof...(AIndex) + sizeof...(BIndex)>(gen_seq<sizeof...(AIndex) + sizeof...(BIndex)>(), str1.str[AIndex]..., str2.str[BIndex]...);
    }

    template <int... Index>
    constexpr static String<sizeof...(Index) + 1> Add(const String<sizeof...(Index)>& str1, char c, seq<Index...>)
    {
        // 调用constr::String的构造函数3来实现加一个字符
        return String<sizeof...(Index) + 1>(gen_seq<sizeof...(Index) + 1>(), str1.str[Index]..., c);
    }

    template <int... Index>
    constexpr static String<sizeof...(Index) + 1> Add(char c, const String<sizeof...(Index)>& str1, seq<Index...>)
    {
        // 调用constr::String的构造函数3来实现加一个字符
        return String<sizeof...(Index) + 1>(gen_seq<sizeof...(Index) + 1>(), c, str1.str[Index]...);
    }
};

// 字符串加字符串
template<int ASize, int BSize>
constexpr String<ASize + BSize> operator+(const String<ASize>& str1, const String<BSize>& str2)
{
    return Assist::Add(str1, str2, gen_seq<ASize>(), gen_seq<BSize>());
}

// 后加一个字符
template<int Size>
constexpr String<Size + 1> operator+(const String<Size>& str, char c)
{
    return Assist::Add(str, c, gen_seq<Size>());
}

// 前加一个字符
template<int Size>
constexpr String<Size + 1> operator+(char c, const String<Size>& str)
{
    return Assist::Add(c, str, gen_seq<Size>());
}

// 后加一个字符串 例: constr::str("123") + "abc";
template<int Size1, int I>
constexpr String<Size1 + I - 1> operator+(const String<Size1>& str1, const char(&str2)[I])
{
    return str1 + String<I - 1>(str2);
}

// 前加一个字符串 例: "abc" + constr::str("123");
template<int Size1, int I>
constexpr String<Size1 + I - 1> operator+(const char(&str1)[I], const String<Size1>& str2)
{
    return String<I - 1>(str1) + str2;
}

template<int I>
constexpr String<I - 1> Str(const char(&a)[I])
{
    return String<I - 1>(a);
}

constexpr String<0> Str()
{
    return String<0>();
}

constexpr String<1> Str(char c)
{
    return String<1>(c);
}

}

Тест:

      int main(int argc, char** argv)
{
    constexpr auto demo_1 = constr::Str("123");
    constexpr auto demo_2 = constr::Str({ '4', '5', '6', '\0'});
    constexpr auto demo_3 = demo_1 + demo_2;
    constexpr auto demo_4 = demo_1 + 'a';
    constexpr auto demo_5 = 'a' + demo_1;
    constexpr auto demo_6 = demo_1 + "abc";
    constexpr auto demo_7 = "abc" + demo_1 ;
    constexpr auto demo_8 = constr::Str('a');
    constexpr auto demo_9 = constr::Str();

    std::cout << "demo_3.size = " << demo_3.size << std::endl;
    std::cout << "demo_3.str = " << demo_3.str << std::endl;
    std::cout << "demo_4.str = " << demo_4.str << std::endl;
    std::cout << "demo_5.str = " << demo_5.str << std::endl;
    std::cout << "demo_6.str = " << demo_6.str << std::endl;
    std::cout << "demo_7.str = " << demo_7.str << std::endl;
    std::cout << "demo_8.str = " << demo_8.str << std::endl;
    std::cout << "demo_9.str = " << demo_9.str << std::endl;
    
    return 0;
}

Результат испытаний:

      demo_3.size = 6
demo_3.str = 123456
demo_4.str = 123a
demo_5.str = a123
demo_6.str = 123abc
demo_7.str = abc123
demo_8.str = a
demo_9.str =

Версия без лямбда, использующая std::min и sizeof. Длина строки ограничена 256. Это можно использовать в невычисленном контексте
, таком как decltype или sizeof. Я использовал макросы штампа, чтобы уменьшить размер кода.

      #include <type_traits>
#include <utility>


template <char...>
struct Str
{
};

namespace char_mpl
{

constexpr auto first(char val, char...)
{
    return val;
}
constexpr auto second(char, char val, char...)
{
    return val;
}

template <class S1, class S2>
struct Concat;

template <char... lefts, char... rights>
struct Concat<Str<lefts...>, Str<rights...>>
{
    using type = Str<lefts..., rights...>;
};


template <size_t right_count, class Right>
struct Take;

template <template <char...> class Right, char... vals>
struct Take<0, Right<vals...>>
{
    using type = Str<>;
};

template <template <char...> class Right, char... vals>
struct Take<1, Right<vals...>>
{
    using type = Str<first(vals...)>;
};

template <template <char...> class Right, char... vals>
struct Take<2, Right<vals...>>
{
    using type = Str<first(vals...), second(vals...)>;
};

template <size_t lhs, size_t rhs>
concept greater = lhs > rhs;

// this may be improved for speed.
template <size_t n, char left, char... vals>
requires greater<n, 2> struct Take<n, Str<left, vals...>>
{
    using type =
        Concat<Str<left>,                              //
               typename Take<n - 1, Str<vals...>>::type//
               >::type;
};

};// namespace char_mpl


template <int length, char... vals>
struct RawStr
{
    constexpr auto ch(char c, int i)
    {
        return c;
    }

    constexpr static auto to_str()
    {
        return
            typename char_mpl::Take<length,
                                    Str<vals...>>::type{};
    }
};

#define STAMP4(n, STR, stamper)                            \
    stamper(n, STR) stamper(n + 1, STR)                    \
        stamper(n + 2, STR) stamper(n + 3, STR)
#define STAMP16(n, STR, stamper)                           \
    STAMP4(n, STR, stamper)                                \
    STAMP4(n + 4, STR, stamper)                            \
    STAMP4(n + 8, STR, stamper)                            \
    STAMP4(n + 12, STR, stamper)
#define STAMP64(n, STR, stamper)                           \
    STAMP16(n, STR, stamper)                               \
    STAMP16(n + 16, STR, stamper)                          \
    STAMP16(n + 32, STR, stamper)                          \
    STAMP16(n + 48, STR, stamper)
#define STAMP256(n, STR, stamper)                          \
    STAMP64(n, STR, stamper)                               \
    STAMP64(n + 64, STR, stamper)                          \
    STAMP64(n + 128, STR, stamper)                         \
    STAMP64(n + 192, STR, stamper)

#define STAMP(n, STR, stamper) stamper(STAMP##n, STR, n)


#define CH(STR, i) STR[std::min<size_t>(sizeof(STR) - 1, i)]


#define CSTR_STAMPER_CASE(n, STR) CH(STR, n),

#define CSTR_STAMPER(stamper, STR, n)                      \
    RawStr<sizeof(STR) - 1,                                \
           stamper(0, STR, CSTR_STAMPER_CASE)              \
               CH(STR, 256)>

#define CSTR(STR) (STAMP(256, STR, CSTR_STAMPER){}).to_str()


int main()
{
    constexpr auto s = CSTR("12345");
    decltype(CSTR("123123"));
    sizeof(CSTR("123123"));
    static_assert(
        std::is_same_v<
            Str<'1'>,
            std::remove_cvref_t<decltype(CSTR("1"))>>);
    static_assert(
        std::is_same_v<
            Str<'1', '2'>,
            std::remove_cvref_t<decltype(CSTR("12"))>>);
    static_assert(
        std::is_same_v<
            Str<'1', '2', '3', '4', '5'>,
            std::remove_cvref_t<decltype(CSTR("12345"))>>);
}

Вам нужны шаблоны литеральных операторов N3599 для строк . Он был предложен для C++ в 2013 году, но не было единого мнения по деталям, и он так и не был добавлен в стандарт.

Однако GCC и Clang поддерживают его как расширение. Он позволяет разделить строковые литералы на набор символов шаблона параметров:

      // some template type to represent a string
template <char... chars>
struct TemplateString {
    static constexpr char value[] = { chars... };
    
    template <char... chars2>
    constexpr auto operator+(TemplateString<chars2...>) const {
        // compile-time concatenation, oh yeah!
        return TemplateString<chars..., chars2...>{};
    }
};

// a custom user-defined literal called by the compiler when you use your _suffix
template <typename CharType, CharType... chars>
constexpr auto operator""_tstr () {
    // since all the chars are constants here, you can do compile-time
    // processing with constexpr functions and/or template metaprogramming,
    // and then return whatever converted type you like
    return TemplateString<chars...>{};
}


// auto = TemplateString<'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'>
constexpr auto str = "Hello"_tstr + " world!"_tstr;
cout << str.value << endl;

В качестве запасного варианта трюки с использованием макроса приводят вас в одно и то же место (как показано в ответе, например, smilethax ).

Обратите внимание, что это единственные два способа принять строковые литералы и разделить их на символы constexpr: либо вы используете расширение, либо используете макро-хакерство на сайте вызова.

Решение @smilingthax может быть короче, если использовать std::index_sequence:

      template<char...>
struct Str {};

template<class T, size_t... Is>
[[nodiscard]] constexpr auto helper(std::index_sequence<Is...>) {
    return Str<T{}.chars[Is]...>{};
}

#define STR(str)                                                          \
    [] {                                                                  \
        struct Temp {                                                     \
            const char* chars = str;                                      \
        };                                                                \
        return helper<Temp>(std::make_index_sequence<sizeof(str) - 1>{}); \
    }()

или еще короче:

      template<char...>
struct Str {};

#define STR(str)                                   \
    []<size_t... Is>(std::index_sequence<Is...>) { \
        return Str<str[Is]...>{};                  \
    }                                              \
    (std::make_index_sequence<sizeof(str) - 1>{})

Адаптировано из ответа #QuarticCat

      template <char...>
struct Str
{
};

#define STRNAME(str) _constexpr_string_type_helper_##str
#define STR(str)                                                     \
    auto STRNAME(str) = []<size_t... Is>(std::index_sequence<Is...>) \
    {                                                                \
        constexpr char chars[] = #str;                               \
        return Str<chars[Is]...>{};                                  \
    }                                                                \
    (std::make_index_sequence<sizeof(#str) - 1>{});                  \
    decltype(STRNAME(str))

Полный код здесь

Я хотел бы добавить два очень небольших улучшения в ответ @user1115339. Я упомянул их в комментариях к ответу, но для удобства я выложу здесь решение для копирования.

Единственная разница заключается в FIXED_CSTRING макрос, который позволяет использовать строки в шаблонах классов и в качестве аргументов оператора индекса (полезно, если у вас есть, например, карта времени компиляции).

Живой пример.

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")];
}
Другие вопросы по тегам