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

93759 次浏览

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

有一篇文章: 在 C + + 模板元程序中使用字符串,作者是 Abel Sinkovics 和 Dave Abrahams。

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

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

加上条件 Push _ back


我改变了我对 Yankes 解决方案的接受答案,因为它解决了这个特定的问题,而且这样做非常优雅,没有使用 Constexpr 或复杂的预处理器代码。

如果你接受后跟零,手写宏循环,2倍重复字符串在扩展宏,没有 Boost-那么我同意-这是更好的。不过,在 Boost 中,它只有三句台词:

现场演示

#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

如果你不想使用 加速溶液,你可以创建一些简单的宏来做类似的事情:

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

编辑: 正如 Howard Hinant (以及我在对 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();
}
 

我没有看到任何东西可以与 Scott Schurr 的 str_constC + + Now 20122012年上的优雅相媲美,但是它确实需要 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 + + 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!
}

基于从 霍华德 · 希南特的想法,您可以创建文字类,将两个文字相加在一起。

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"

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

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

我相信应该可以定义一个 C 预处理器宏 获取一个字符串和该字符串的大小作为参数,并返回一个 由字符串中的字符组成的序列(使用 BOOST _ PP _ FOR、字符串化、数组下标等)。 但是,我没有时间(或者足够的兴趣)去实现这些 宏

它可以不依赖于升级来实现,使用非常简单的宏和一些 C + + 11特性:

  1. Lambdas 变数
  2. 模板
  3. 广义常数表达式广义常数表达式
  4. 非静态数据成员初始值设定项
  5. 统一初始化

(这里并不严格要求后两者)

  1. 我们需要能够用用户提供的从0到 N 的索引来实例化一个可变参数模板——这个工具对于将 tuple 扩展到可变参数模板函数的参数也很有用(参见问题: < a href = “ https://stackoverflow./questions/687490/How-do-i-exp- a-tuple-into-variad-template-function-uments”> 如何将 tuple 扩展到可变参数模板函数的参数?
    “解压缩”一个元组来调用匹配的函数指针 )

    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. then define a variadic template called string with non-type parameter 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. now the most interesting part - to pass character literals into string template:

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

a simple concatenation demonstration shows the usage:

    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 + + 14解决方案,可以为传递的每个编译时字符串创建一个 std: : tuple < char... > 。

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

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

你的方法1是正确的。

但是,数组需要有外部链接,因此要使方法1工作,我们必须编写如下代码: Conexpr 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 指针提到的那样,但是只针对数字。 在此之前,另一个小技巧是使用覆盖编辑模式 + 复制和粘贴

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

似乎没有人喜欢我的另一个回答:。因此,我在这里展示了如何将 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)编译

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

当我在玩增强花图的时候,我偶然发现了这个线索。由于没有一个答案解决了我的问题,我找到了一个不同的解决方案,我想在这里添加,因为它可能对其他人有潜在的帮助。

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

由于这个线程中的解决方案要么使用 lambda,要么不为不同的内容提供不同的类型,我发现下面的方法很有帮助。此外,它还避免了蹩脚的 str<'a', 'b', 'c'>语法。

其基本思想是将 Scott Schurr 的 str_const的一个版本作为人物散列的模板。它是 c++14,但是使用 crc32函数的递归实现 c++11应该是可能的(参见 给你)。

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


#include <string>


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




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


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


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


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


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


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


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

用法:

#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-cl5.0产生的汇编程序代码如下:

012A1370  mov         eax,2
012A1375  ret

我想为@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")];
}

我自己的实现是基于 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

有关其他用法的详细信息,请参阅测试。

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

在 C + + 17中,使用 helper 宏函数很容易创建编译时字符串:

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

@ milingthax 的解决方案可以通过使用 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))

这里是完整代码

Non lambda 版本,使用 std: : min 和 sizeof。
绳子的长度是有限的到256。
这可以在 未经评估的上下文中使用,比如 dectype 或 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文字运算符模板。它是在2013年提出的 C + + ,但 没有达成共识的细节,它从来没有被添加到标准。

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

作为备用方案,使用宏的技巧可以让您到达相同的位置(例如,如 用微笑回答所示)。

请注意,这是 只有接受字符串并将其拆分为 Constexpr 字符的两种方法: 要么使用扩展名,要么在调用站点上使用宏黑客技术。