在 C++ 中方便地声明编译时字符串

Posted

技术标签:

【中文标题】在 C++ 中方便地声明编译时字符串【英文标题】:Conveniently Declaring Compile-Time Strings in C++ 【发布时间】:2013-03-29 07:08:47 【问题描述】:

能够在 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,字符串化,数组下标,之类的)。但是,我没有时间(或足够的兴趣)来实现这样的宏 =)

【问题讨论】:

Boost 有一个宏,它定义了一个可以用作常量表达式的字符串。好吧,它定义了一个具有字符串成员的类。你检查了吗? 你检查cpp-next.com/archive/2012/10/…了吗? Stack Overflow 不适合询问是否存在某项提案。最好的地方是the C++ site。 基本上,您将存储在 array/ptr 中的字符扩展为参数包(就像 Xeo 所做的那样)。虽然它们没有拆分为非类型模板参数,但您可以在 constexpr 函数中使用它们并初始化数组(因此,concat、substr 等)。 @MareInfinitus 简而言之,constexpr 字符串可以在编译时解析,因此您可以根据结果采用不同的代码路径。本质上,您可以在 C++ 中创建 EDL;应用程序非常无限。 【参考方案1】:

我还没有看到与Scott Schurr's 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!

没有比编译时范围检查更酷的了!

无论是使用还是实现,都没有宏。并且对字符串大小没有人为的限制。我会在这里发布实现,但我尊重 Scott 的隐含版权。实现是在上面链接的他的演示文稿的单张幻灯片上。

更新 C++17

自从我发布此答案以来的几年里,std::string_view 已成为我们工具箱的一部分。以下是我将如何使用string_view 重写上述内容:

#include <string_view>

int
main()

    constexpr std::string_view my_string = "Hello, world!";
    static_assert(my_string.size() == 13);
    static_assert(my_string[4] == 'o');
    constexpr std::string_view my_other_string = my_string;
    static_assert(my_string == my_other_string);
    constexpr std::string_view world(my_string.substr(7, 5));
    static_assert(world == "world");
//  constexpr char x = world.at(5); // Does not compile because index is out of range!

【讨论】:

创建新的 constexpr 字符串的操作(如字符串连接和子字符串提取)可以使用这种方法吗?也许使用两个 constexpr-string 类(一个基于str_const,另一个基于sequence),这是可能的。用户将使用str_const 来初始化字符串,但创建新字符串的后续操作将返回sequence 对象。 这是一段很好的代码。但是,与使用字符序列作为模板参数声明的字符串相比,这种方法仍然存在缺陷:str_const 是一个常量值,而不是类型,因此阻止了许多元编程习语的使用。 @JBJansen,可以在没有散列函数的情况下将字符串编译为可用作模板参数的类型。每个不同的字符串给出不同的类型。基本思想是将字符串转成字符包template&lt;char... cs&gt;。理论上,您可以构建一个接受文字字符串并将内容编译为函数的东西。请参阅 dyp 的答案。一个非常完整的库是metaparse。本质上,您可以定义从文字字符串到类型的任何映射,并使用这种技术实现它。 我不喜欢这种热情......不适用于模板元函数 - 非常 烦人,因为 constexpr 函数应在运行时调用的愚蠢折衷 - 没有真正的连接, 需要定义一个 char 数组(在标题中很难看)——尽管由于前面提到的 constexpr 妥协,大多数无宏解决方案都是如此——并且范围检查并没有给我留下太多印象,因为即使是低级的 constexpr const char * 也有。我推出了自己的参数包字符串,它也可以由文字(使用元函数)以数组定义为代价。 @user975326:我刚刚查看了我的实现,看起来我添加了一个constexpr operator==。对不起。 Scott 的演示文稿应该让您开始了解如何执行此操作。在 C++14 中比在 C++11 中容易得多。我什至不会费心尝试 C++11。在这里查看 Scott 最新的 constexpr 会谈:youtube.com/user/CppCon【参考方案2】:

我相信应该可以定义一个 C 预处理器宏 接受一个字符串和字符串的大小作为参数,并返回一个 由字符串中的字符组成的序列(使用 BOOST_PP_FOR、字符串化、数组下标等)。 但是,我没有时间(或足够的兴趣)来实施这样的 一个宏

使用非常简单的宏和一些 C++11 特性可以在不依赖 boost 的情况下实现这一点:

    lambdas 可变参数 模板 广义常量表达式 非静态数据成员初始化器 统一初始化

(后两者此处不严格要求)

    我们需要能够使用用户提供的从 0 到 N 的索引来实例化一个可变参数模板——该工具也很有用,例如将元组扩展为可变参数模板函数的参数(请参阅问题:How do I expand a tuple into variadic template function's arguments?"unpacking" a tuple to call a matching function pointer)

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

    现在最有趣的部分 - 将字符文字传递给字符串 模板:

    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

【讨论】:

这太简单了,我仍然不敢相信它有效。 +1!一件事:你不应该使用 size_t 而不是 unsigned 吗? 那么使用operator+ 代替operator* 怎么样? (str_hello + str_world) 我更喜欢这个解决方案而不是流行的 Scott Schurr 的 str_const 方法,因为这个方法确保底层数据是 constexpr。 Schurr 的方法让我可以在运行时使用 char[] 堆栈变量创建 str_const;我无法安全地从函数返回 str_const 或将其传递给另一个线程。 链接已失效...谁能重新发布? @格伦? 您应该在 CSTRING 宏中的 lambda 周围添加一对额外的大括号。否则,您无法在对 [] 运算符的调用中创建 CSTRING,因为双精度 [[ 是为属性保留的。【参考方案3】:

编辑:正如 Howard Hinnant(以及我在对 OP 的评论中所指出的)所指出的,您可能不需要将字符串的每个字符都作为单个模板参数的类型。 如果你确实需要这个,下面有一个无宏的解决方案。

我在编译时尝试使用字符串时发现了一个技巧。需要引入除“模板字符串”之外的另一种类型,但在函数内部,可以限制该类型的范围。

它不使用宏,而是使用一些 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);


// destination "template string" type
template < char... chars >
struct exploded_string

    static void print()
    
        char const str[] =  chars... ;
        std::cout.write(str, sizeof(str));
    
;

// struct to explode a `char const*` to an `exploded_string` type
template < typename StrProvider, unsigned len, char... chars  >
struct explode_impl

    using result =
        typename explode_impl < StrProvider, len-1,
                                StrProvider::str()[len-1],
                                chars... > :: result;
;

    // recursion end
    template < typename StrProvider, char... chars >
    struct explode_impl < StrProvider, 0, chars... >
    
         using result = exploded_string < chars... >;
    ;

// syntactical sugar
template < typename StrProvider >
using explode =
    typename explode_impl < StrProvider,
                            c_strlen(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();

 

【讨论】:

我刚刚花了一个周末独立开发了一段类似的代码,并制作了一个非常基本的系统来解析类型字符串,例如pair&lt;int,pair&lt;char,double&gt;&gt;。我为自己感到自豪,然后发现了这个答案,以及今天的 metaparse 库!在开始像这样的愚蠢项目之前,我真的应该更彻底地搜索一下 :-) 我想,理论上,可以用这种技术构建一个完全 C++ 编译器。用这个构建的最疯狂的东西是什么? 我不知道。我从来没有在实际项目中真正使用过这些技术,所以我没有追求这种方法。虽然我想我记得本地类型技巧的一个细微变化,它稍微方便一些......也许是一个本地静态char[] 你的意思是my_str.print();而不是str.print(); 是否有 C++ 14 稍短的版本? 代替递归打印机,我认为更简单的选择是char str[] = ttc...; std::cout &lt;&lt; str &lt;&lt; std::endl;【参考方案4】:

如果你不想使用Boost solution,你可以创建简单的宏来做类似的事情:

#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 个字符的固定大小(加上额外的零)。但它可以根据您的需要轻松更改。

【讨论】:

我非常喜欢这个解决方案;它非常简单,并且可以优雅地完成工作。是否可以修改宏以便不附加 sizeof(str) &gt; i (而不是附加额外的 0, 标记)?定义一个 trim 元函数很容易,它会在宏被调用后执行此操作,但如果可以修改宏本身就更好了。 不可能,因为解析器不理解sizeof(str)。可以手动添加字符串大小,如 MACRO_GET_STR(6, "Hello"),但这需要 Boost 宏才能工作,因为手动编写它需要 100 倍以上的代码(你需要实现像 1+1 这样的简单东西)。【参考方案5】:

我相信应该可以定义一个 C 预处理器宏,它将字符串和字符串的大小作为参数,并返回由字符串中的字符组成的序列(使用 BOOST_PP_FOR、字符串化、数组下标和喜欢)

有一篇文章:Using strings in C++ template metaprograms,作者是 Abel Sinkovics 和 Dave Abrahams。

它比您使用宏 + BOOST_PP_REPEAT 的想法有了一些改进 - 它不需要将显式大小传递给宏。简而言之,它基于字符串大小的固定上限和“字符串溢出保护”:

template <int N>
constexpr char at(char const(&s)[N], int i)

    return i >= N ? '\0' : s[i];

加上条件boost::mpl::push_back


我将接受的答案更改为 Yankes 的解决方案,因为它解决了这个特定问题,并且在不使用 constexpr 或复杂的预处理器代码的情况下优雅地解决了问题。

如果您接受尾随零、手写宏循环、2x 在扩展宏中重复字符串,并且没有 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

【讨论】:

我最初将解决方案更改为 Yankes',因为他在这里提供了第一个工作示例。在这一点上,有很多很好的竞争想法。这么早选择答案是我的错误。我目前将此问题标记为未回答,并推迟到我有时间尝试每个人在此处发布的想法。人们在这里给出的答案中有很多有用的信息...... 我同意 - 例如,我喜欢 Howard Hinnant 的例子。【参考方案6】:

一位同事要求我在编译时连接内存中的字符串。它还包括在编译时实例化单个字符串。完整的代码清单在这里:

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

【讨论】:

你确定它是在编译时完成的?前段时间有a discussion about this,对我来说,结果还不清楚。 运行objdump -t a.out |grep my 什么也没找到。当我开始输入这段代码时,我一直在尝试从函数中删除constexpr,当constexpr 被省略时,objdump 会显示它们。我有 99.9% 的把握它发生在编译时。 如果您查看反汇编代码 (-S),您会注意到 gcc (4.7.2) 确实在编译时解析了 constexpr 函数。然而,字符串不是在编译时组装的。相反,(如果我解释正确的话)对于这些“组装”字符串的每个字符,都有一个自己的 movb 操作,这可以说是您正在寻找的优化。 确实如此。我再次尝试使用 gcc 4.9,它仍然做同样的事情。我一直认为这是编译器很愚蠢。直到昨天我才想到尝试不同的编译器。使用 clang,字节方向的 movs 根本不存在。使用 gcc,-Os 也可以摆脱它们,但 -O3 做同样的事情。【参考方案7】:

这是一个简洁的 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");

用户定义的文字还不能用于此,真是太糟糕了。

【讨论】:

实际上,他们可以使用 GCC/Clang 支持的扩展,但我要等到将其添加到标准中,然后再将其作为答案发布。【参考方案8】:

似乎没有人喜欢我的其他答案:-

#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) 编译

【讨论】:

效果很好,但不适用于 msvc 2019,因为它抱怨 str.size() 不是 constexpr。可以通过使用单独推导 str.size() 添加第二个来修复。也许这阻碍了一些支持;-)【参考方案9】:

您的方法 #1 是正确的。

但是,数组需要有外部链接,所以要让方法 1 起作用,我们必须编写如下内容: constexpr const char str[] = "你好,世界!";

不,不正确。这使用 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 也提到的那样,但仅限于数字。 在那之前还有一个小技巧就是使用override编辑模式+复制粘贴

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

如果您不介意宏,则可以使用(根据 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!)>();

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

在玩 boost hana 地图时,我遇到了这个帖子。由于没有一个答案解决了我的问题,我找到了一个不同的解决方案,我想在此处添加它,因为它可能对其他人有潜在的帮助。

我的问题是,当使用带有 hana 字符串的 boost hana 映射时,编译器仍会生成一些运行时代码(见下文)。原因很明显,要在编译时查询地图,它必须是constexpr。这是不可能的,因为BOOST_HANA_STRING 宏会生成一个无法在constexpr 上下文中使用的lambda。另一方面,地图需要不同内容的字符串才能成为不同的类型。

由于该线程中的解决方案要么使用 lambda,要么不为不同的内容提供不同的类型,我发现以下方法很有帮助。它还避免了 hacky str&lt;'a', 'b', 'c'&gt; 语法。

基本思想是将 Scott Schurr 的 str_const 版本以字符哈希为模板。它是c++14,但c++11 应该可以通过crc32 函数的递归实现来实现(参见here)。

// 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://***.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  

【讨论】:

【参考方案12】:

在带有辅助宏函数的 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);

【讨论】:

【参考方案13】:

基于Howard Hinnant 的想法,您可以创建将两个文字相加的文字类。

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" 

【讨论】:

str_at 来自哪里? 它是这样的:str_at&lt;int I&gt;(const char* a) return a[i]; 【参考方案14】:

我想对@user1115339 的answer 添加两个非常小的改进。我在答案的 cmets 中提到了它们,但为方便起见,我将在此处放置一个复制粘贴解决方案。

唯一的区别是 FIXED_CSTRING 宏,它允许在类模板中使用字符串并作为索引运算符的参数(如果您有编译时映射,则很有用)。

Live example.

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

【讨论】:

【参考方案15】:

我自己的实现基于 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#tutorial-appendix-constexpr

有关其他使用详情,请参阅测试。

整个项目目前处于试验阶段。

【讨论】:

【参考方案16】:

改编自#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))

Full code here

【讨论】:

【参考方案17】:

非 lambda 版本,使用 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"))>>);

【讨论】:

【参考方案18】:

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

【讨论】:

【参考方案19】:

您要查找的是 N3599 Literal operator templates for strings。它是在 2013 年针对 C++ 提出的,但在细节方面there was no consensus 从未添加到标准中。

但是,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;

作为一种后备,使用宏的技巧可以将您带到同一个地方(例如,answer by smilingthax 中所示)。

请注意,只有两种接受字符串文字并将其拆分为 constexpr 字符的方法:要么使用扩展,要么使用宏hackery呼叫站点。

【讨论】:

以上是关于在 C++ 中方便地声明编译时字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中声明一个字符串数组?

如何在 C++ 中声明一个字符串而不赋值?

C++ / 类编译和字符串属性:“在 '=' 标记之前需要 `)'

如何在 C 中安全地声明 16 位字符串文字?

C++变量类型

在编译的 exe 中编辑字符串变量? C++ win32