在常量表达式中是否可以使用std::string?

297
使用C++11,Ubuntu 14.04,GCC默认工具链。
这段代码失败了:
constexpr std::string constString = "constString";

错误:constexpr变量'constString'的类型'const string {aka const std::basic_string}'不是字面值类型...因为...'std::basic_string'具有非平凡的析构函数。
是否可以在constexpr中使用std::string?(显然不行...)如果可以,如何使用?是否有其他方法可以在constexpr中使用字符串?

7
std::string 不是字面类型(literal type)。 - Piotr Skotnicki
19
@PiotrS - 这个问题说... - Vector
6
@Vector,我是否问过你什么是constexpr或为什么要使std::string成为constexpr?stackoverflow上有几种编译时字符串实现。如果你已经理解了错误信息并且只有字面值类型可以成为constexpr,那么询问非文字类型是否可以成为constexpr有何意义?此外,有几个原因可能需要一个constexpr实例,因此我建议您澄清您的问题。 - Piotr Skotnicki
4
正如@PiotrS所说,有一些实现了constexpr的字符串。std::string不是其中之一。 - tenfour
8
@PiotrS - 《SO上有几种编译时字符串实现》- 好的,谢谢,我明白了。虽然这对我来说不是一个选项,但它回答了我的问题:没有办法让std::string起作用。正如我对tenfour所说的那样,我想知道是否有一种使用std::string的方式可以让它起作用。肯定有很多技巧是我不知道的。 - Vector
显示剩余9条评论
5个回答

287
截至 C++20,是的,但只有当 std::string 在常量求值结束时被销毁。因此,尽管您的示例仍无法编译,类似以下示例的代码可以:
constexpr std::size_t n = std::string("hello, world").size();

然而,从 C++17 开始,您可以使用 string_view
constexpr std::string_view sv = "hello, world";

string_view 是类似于string的对象,它作为一个不可变的、非所有者的引用,引用到任何一系列 char 对象。


21
注意,每当您将此常量传递给一个接受“const std::string&”参数的函数时,都必须构造一个新的std::string。这通常与创建常量时的初衷相反。因此,我倾向于认为这不是一个好主意。至少您需要小心。 - Rambo Ramon
55
string_view 不能隐式转换为 string,因此很难意外地从 string_view 构造出 string。相反,char const* 可以隐式转换为 string,所以在这个意义上使用 string_view 更加安全。请注意,本翻译仅提供通用参考,具体语境可能需要调整。 - Joseph Thomson
12
感谢澄清。我完全同意,确实忘记了 string_view 不能隐式转换为 string。依我之见,我提出的问题仍然存在,但不适用于 string_view。事实上,正如您所提到的,这方面甚至更加安全。 - Rambo Ramon
7
若此答案能更详细说明string_view是什么,而非只提供一个链接,那将非常好。 - eric
4
请注意,虽然 std::string 对象有一个 .c_str() 方法,它返回以 NULL 结尾的 char* 字符串指针,但是 string_view 对象没有这个方法。与 std::string 类似,string_view 有一个 .data() 方法,它返回一个 char* 字符串指针,但是不能保证以 NULL 结尾(如果 string_view 是另一个字符串的视图,而该字符串内部没有 NULL,则不能以 NULL 结尾)。如果您从编译时常量 char 数组初始化 string_view,那么它将会以 NULL 结尾,但是如果需要与系统调用一起使用,请小心接受 string_view - Perkins
显示剩余5条评论

246

不行,你的编译器已经给你提供了全面的解释。

但是你可以这样做:

constexpr char constString[] = "constString";
在运行时,这可以用于在需要时构建一个std::string

113
为什么不使用constexpr auto constString = "constString";呢?没必要使用那种丑陋的数组语法;-) - stefan
130
在这个问题的背景下,意思更加清晰。我的观点是关于你可以选择哪些字符串类型的。当我试图强调要使用的数据类型时,char[]auto更冗长/清晰。 - tenfour
12
@tenfour 没错,那是个好观点。我想有时我可能过于注重使用“auto”了;-) - stefan
9
在这个情境中,对 char 数组使用 constexpr 有意义吗?如果你用它来构建一个字符串,那么它将无论如何被复制。把字面值传递给字符串的构造函数和把这样的 constexpr 数组传递给它有什么区别? - KjMag
13
通常我更喜欢使用auto。但是有一点微妙的区别:auto将被推断为const char* const,因此sizeof(constString)将产生8。然而,使用char constString[]语法,sizeof(constString)将产生字符串中字符的数量,这可能是人们所期望的。 - pasbi
显示剩余10条评论

48

C++20将添加constexpr字符串和向量

以下提案已被接受http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0980r0.pdf,它添加了诸如以下构造函数:

// 20.3.2.2, construct/copy/destroy
constexpr
basic_string() noexcept(noexcept(Allocator())) : basic_string(Allocator()) { }
constexpr
explicit basic_string(const Allocator& a) noexcept;
constexpr
basic_string(const basic_string& str);
constexpr
basic_string(basic_string&& str) noexcept;

除了所有/大部分方法的constexpr版本之外。

目前GCC 9.1.0还不支持,以下代码无法编译:

#include <string>

int main() {
    constexpr std::string s("abc");
}

使用:

g++-9 -std=c++2a main.cpp

有错误:

error: the type ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} of ‘constexpr’ variable ‘s’ is not literal

std::vector无法创建constexpr std::vector中讨论。

在Ubuntu 19.04中进行了测试。


22

问题在于其析构函数不是微不足道的,因此如果将析构函数从std::string中删除,则可以定义该类型的constexpr实例。 像这样:

struct constexpr_str {
    char const* str;
    std::size_t size;

    // can only construct from a char[] literal
    template <std::size_t N>
    constexpr constexpr_str(char const (&s)[N])
        : str(s)
        , size(N - 1) // not count the trailing nul
    {}
};

int main()
{
    constexpr constexpr_str s("constString");

    // its .size is a constexpr
    std::array<int, s.size> a;
    return 0;
}

29
基本上,C++17中的string_view就是这个意思,除了string_view提供了大部分你从std::string中了解的功能。 - wich
这只是一个小细节,但我想指出的是,“只能从char[]文字构造”并不完全正确。您可以构造一个本地数组,例如char test [6] {“hello”}; constexpr_str s(test);。我刚刚遇到了这个问题,所以想指出一下。如果你真的想要强制使用字面量,一个替代方法是将“const char*”作为模板非类型参数传递,尽管这也有点棘手,具体取决于C++版本。 - Alexander Torstling

13

C++20是朝着使得在编译时使用std::string变得可能的一步,但P0980并不允许您编写像您问题中的代码那样的代码:

C++20是为了实现在编译时使用std::string而迈出的一步,但是P0980不允许你编写像你提问中那样的代码。
constexpr std::string constString = "constString";

之所以会这样,是因为constexprstd::string仅允许在constexpr函数(常量表达式计算上下文)中使用。由constexprstd::string分配的内存必须在函数返回之前被释放——这就是所谓的短暂分配,并且这些内存不能泄露到运行时中对于constexpr对象(存储在数据段中)的“泄漏”。例如,在当前 VS2022 预览版(cl 版本:19.30.30704)中编译上面那行代码会导致以下错误:

1> : error C2131: expression did not evaluate to a constant
1> : message : (sub-)object points to memory which was heap allocated during constant evaluation

这是因为它试图进行一次非瞬时分配,而这是不允许的 - 这意味着要将内存分配到已编译二进制文件的数据段中。

在 p0784r1 中的“非瞬时(Non-transient)分配”一节中,您可以发现有一个计划允许将瞬时内存转换为静态内存(重点在于“我”):

那么对于在评估完成时尚未被解除分配的存储怎么办?我们可以禁止这样做,但确实存在非常强烈的使用情况。例如,这可能是更灵活的“字符串字面量”类的基础。因此,我们建议,如果非瞬时 constexpr 分配有效(将在下面描述),则分配的对象将提升为静态存储期。

有一种方法可以导出瞬时的 std::string 数据以便在运行时使用它。您必须将其复制到 std::array 中,问题是计算最终的 std::array 大小,您可以预设一些大的大小或两次计算 std::string - 一次获取大小,一次获取实际数据。以下代码成功地在当前的 VS2022 预览版 5 上编译并运行。它基本上是使用分隔符连接三个单词:

constexpr auto join_length(const std::vector<std::string>& vec, char delimiter) {
  std::size_t length = std::accumulate(vec.begin(), vec.end(), 0,
    [](std::size_t sum, const std::string& s) {
      return sum + s.size();
    });
  return length + vec.size();
}

template<size_t N>
constexpr std::array<char, N+1> join_to_array(const std::vector<std::string>& vec, char delimiter) {
  std::string result = std::accumulate(std::next(vec.begin()), vec.end(),
    vec[0],
    [&delimiter](const std::string& a, const std::string& b) {
      return a + delimiter + b;
    });
  std::array<char, N+1> arr = {};
  int i = 0;
  for (auto c : result) {
    arr[i++] = c;
  }
  return arr;
}
constexpr std::vector<std::string> getWords() {
  return { "one", "two", "three" };
}

int main()
{
  constexpr auto arr2 = join_to_array<join_length(getWords(), ';')>(getWords(), ';');
  static_assert(std::string(&arr2[0]) == "one;two;three");
  std::cout << &arr2[0] << "\n";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接