在编译时计算C字符串的长度。这真的是constexpr吗?


94

我正在尝试在编译时计算字符串文字的长度。为此,我使用以下代码:

#include <cstdio>

int constexpr length(const char* str)
{
    return *str ? 1 + length(str + 1) : 0;
}

int main()
{
    printf("%d %d", length("abcd"), length("abcdefgh"));
}

一切正常,程序输出4和8。clang生成的汇编代码表明结果是在编译时计算的:

0x100000f5e:  leaq   0x35(%rip), %rdi          ; "%d %d"
0x100000f65:  movl   $0x4, %esi
0x100000f6a:  movl   $0x8, %edx
0x100000f6f:  xorl   %eax, %eax
0x100000f71:  callq  0x100000f7a               ; symbol stub for: printf

我的问题:标准是否保证length函数将在编译时进行评估?

如果这是真的,那么编译时间字符串文字计算的大门就为我打开了。例如,我可以在编译时计算哈希,等等。


3
只要参数是一个常量表达式,它就必须是。
克里斯

1
@chris是否可以保证在不需要常量表达式的上下文中使用时,必须在编译时评估可以是常量表达式的内容?
TC

12
BTW,包括<cstdio>然后调用::printf是不可移植的。该标准仅需<cstdio>提供std::printf
Ben Voigt 2014年

1
@BenVoigt好,谢谢你指出这一点:)最初我使用std :: cout,但是生成的代码非常大,可以找到实际值:)
Mircea Ispas 2014年

3
@Felics 在回答有关优化的问题时,我经常使用Godbolt,并且使用它printf可以减少处理的代码。
Shafik Yaghmour 2014年

Answers:


76

不能保证在编译时对常量表达式进行求值,但是在C ++标准草案的“ 5.19 常量表达式”部分中,我们只有一个非规范性的引号,尽管如此:

[...]> [注意:常量表达式可以在翻译过程中求值。

您可以将结果分配给constexpr变量,以确保在编译时对其进行评估,我们可以从Bjarne Stroustrup的C ++ 11参考资料中看到这一点,该参考资料指出(强调我的意思):

除了能够在编译时评估表达式之外,我们还希望能够要求在编译时评估表达式。变量定义前面的constexpr可以做到这一点(并暗示const):

例如:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup在isocpp博客条目中总结了何时可以确保编译时间评估,并说:

[...]正确的答案-如Herb所说-根据标准,constexpr函数可以在编译时或运行时求值,除非将其用作常量表达式,在这种情况下,必须在编译时求值-时间。为了保证编译时评估,我们必须在需要常量表达式的地方使用它(例如,作为数组绑定或作为大小写标签),或者使用它来初始化constexpr。我希望没有一个自重的编译器会错过执行我最初所说的优化机会:“如果constexpr函数的所有参数都是常量表达式,则它会在编译时求值。”

因此,本文概述了在编译时应对其进行评估的两种情况:

  1. 在需要常量表达式的地方使用它,这似乎在标准草案中使用短语shall be ... converted constant expression或的任何地方shall be ... constant expression,例如数组绑定。
  2. 使用它来初始化constexpr我上面概述的。

4
就是说,原则上,编译器有权查看与内部有链接或没有链接的对象constexpr int x = 5;,请注意在编译时它不需要该值(假设它不用作模板参数或诸如此类),并实际上发出使用5个1和4个加法运算的立即值在运行时计算初始值的代码。一个更现实的示例:编译器可能会遇到递归限制,并将计算推迟到运行时。除非您执行强制编译器实际使用该值的操作,否则“保证在编译时进行评估”是QOI问题。
史蒂夫·杰索普

@SteveJessop Bjarne似乎使用了一个没有类似概念的概念,我可以在标准草案中找到它,用作在翻译时评估的常量表达方式。因此,似乎标准没有明确说明他在说什么,所以我倾向于同意你的看法。尽管Bjarne和Herb似乎都对此表示同意,但这可能表明它的规格不足。
Shafik Yaghmour 2014年

2
我认为他们都只考虑“自重的编译器”,而不是我假设的符合标准但故意破坏性的编译器。它可以用作推理标准实际上保证什么的一种手段,而没有太多其他优点;-)
Steve Jessop 2014年

3
@SteveJessop故意阻塞性编译器,例如臭名昭​​著的(不幸的是不存在的)Hell ++。这样的事情实际上对于测试一致性/可移植性非常有用。
Angew不再为2014年

在假设规则下,即使使用该值作为看似编译时间常数也是不够的:编译器可以自由提供源代码的副本并在运行时重新编译,或进行常规计算以确定a的类型。变量,或者只是constexpr出于邪恶而毫无意义地重新运行您的计算。甚至可以免费在给定的源代码行中每个角色等待1秒,或者采取给定的源代码行并使用它来播种国际象棋位置,然后在双方下棋以确定谁赢了。
Yakk-Adam Nevraumont 2014年

27

找出constexpr函数调用是否导致核心常量表达式真的很容易还是仅仅对其进行优化,:

在需要常量表达式的上下文中使用它。

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}

4
...并使用编译-pedantic(如果使用gcc)。否则,你没有警告和错误
BЈовић

@BЈовић或在GCC没有扩展名可能妨碍扩展的上下文中使用它,例如模板参数。
Angew不再为2014年

不会告发\ t一个枚举黑客更可靠?如enum { Whatever = length("str") }
Sharptooth 2014年

18
值得一提的是static_assert(length("str") == 3, "");
chris

8
constexpr auto test = /*...*/;是最一般,最直接的方法。
TC

19

请注意,现代编译器(例如gcc-4.x)strlen在编译时就可以处理字符串文字,因为通常将其定义为内部函数。没有启用优化。尽管结果不是编译时间常数。

例如:

printf("%zu\n", strlen("abc"));

结果是:

movl    $3, %esi    # strlen("abc")
movl    $.LC0, %edi # "%zu\n"
movl    $0, %eax
call    printf

请注意,这工作,因为strlen是内置的功能,如果我们用-fno-builtins它恢复到在运行时调用它,看到它生活
沙菲克Yaghmour

strlenconstexpr对我来说,即使有-fno-nonansi-builtins(好像-fno-builtins在G不存在任何++以上)。我说“ constexpr”,是因为我可以做到这一点,template<int> void foo();并且可以实现foo<strlen("hi")>(); g ++-4.8.4
Aaron McDaid 2015年

18

让我提出另一个函数,该函数在编译时无需递归地计算字符串的长度。

template< size_t N >
constexpr size_t length( char const (&)[N] )
{
  return N-1;
}

看看ideone的这个示例代码


4
由于嵌入了'\ 0',它可能不等于strlen:strlen(“ hi \ 0there”)!= length(“ hi \ 0there”)
unkulunkulu

这是正确的方法,这是有效的现代C ++中的一个示例(如果我没记错的话)。但是,有一个很好的字符串类,它完全是constexpr,请参见以下答案:Scott Schurr的str_const,这也许会更有用(以及更少的C风格)。
QuantumKarl

@MikeWeir Ops,这很奇怪。这里有各种链接:链接到问题链接到纸张链接到git上的源
QuantumKarl

现在,您要做的是:char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel

7

无法保证 constexpr尽管任何合理的编译器都会在适当的优化级别启用功能,但功能会在编译时进行评估。另一方面,模板参数必须在编译时评估。

我使用以下技巧在编译时强制进行评估。不幸的是,它仅适用于整数值(即不适用于浮点值)。

template<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

现在,如果你写

if (static_eval<int, length("hello, world")>::value > 7) { ... }

您可以确定该if语句是一个编译时常量,没有运行时开销。


8
或仅使用std :: integral_constant <int,length(...)> :: value
Mircea Ispas

1
这个例子是一个位一个无意义的使用,因为len作为constexpr装置length必须在编译时无论如何进行评估。
克里斯

@克里斯我不知道它必须是,虽然我已经观察到,它我的编译器。
5gon12eder

好的,根据它必须执行的大多数其他答案,因此我对示例进行了修改,使其变得没有意义。实际上,这是if我最初使用此技巧的一个条件(在此条件下,编译器必须消除死代码)。
5gon12eder

1

维基百科关于广义常量表达式的条目的简短解释:

在函数上使用constexpr对该函数的功能施加了一些限制。首先,该函数必须具有非空返回类型。其次,函数体无法声明变量或定义新类型。第三,主体可以仅包含声明,空语句和单个return语句。必须存在参数值,以便在参数替换后,return语句中的表达式生成一个常量表达式。

constexpr函数定义之前使用关键字会指示编译器检查是否满足这些限制。如果是,并且使用常量调用该函数,则保证返回的值是常量,因此可以在需要常量表达式的任何地方使用。


这些条件不能保证返回值是恒定的。例如,该函数可能与其他参数值一起调用。
Ben Voigt 2014年

是的,@ BenVoigt。我将其编辑为使用常量表达式进行调用。
kaedinger 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.