【问题标题】:Literals and constexpr functions, compile-time evaluation文字和 constexpr 函数,编译时评估
【发布时间】:2013-06-21 13:03:08
【问题描述】:

尝试通过用户定义的文字实现一个令人愉悦的(简单、直接、没有 TMP、没有宏、没有不可读的复杂代码、没有奇怪的语法)编译时哈希,我发现显然 GCC 对什么是常量表达式与我的理解大相径庭。

既然代码和编译器输出说了一千多个字,那就不用多说了:

#include <cstdio>

constexpr unsigned int operator"" _djb(const char* const str, unsigned int len)
{
    static_assert(__builtin_constant_p(str), "huh?");
    return len ? str[0] + (33 * ::operator"" _djb(str+1, len-1)) : 5381;
}

int main()
{
    printf("%u\n", "blah"_djb);
    return 0;
}

代码非常简单,不需要解释太多,也不需要问太多——除了它不在编译时进行评估。我尝试使用指针取消引用而不是使用数组索引以及在!*str 处进行递归中断,结果都相同。

static_assert 是后来在陷入困境时添加的,原因是当我坚信它应该在编译时不会评估哈希值。好吧,令人惊讶的是,这只让我更加困惑,但并没有澄清任何事情!没有static_assert 的原始代码已被广泛接受并且编译时没有警告(gcc 4.7.2)。

编译器输出:

[...]\main.cpp: In function 'constexpr unsigned int operator"" _djb(const char*, unsigned int)':
[...]\main.cpp:5:2: error: static assertion failed: huh?

我的理解是字符串文字是,嗯... 文字。换句话说,一个编译时常量。具体来说,它是一个编译时已知的常量字符序列,从编译器分配的常量地址开始(因此是已知的),以'\0' 终止。这在逻辑上意味着提供给operator"" 的文字的编译器计算长度也是constexpr

另外,我的理解是,仅使用编译时参数调用 constexpr 函数使其可以作为枚举或模板参数的初始化程序,换句话说,它应该导致在编译时进行评估。
当然,原则上总是允许编译器在运行时评估constexpr 函数,但是能够将评估移至编译时是拥有constexpr 的全部意义,之后全部。

我的谬误在哪里,有没有一种方法可以实现用户定义的文字,它可以采用字符串文字,以便在编译时实际评估?

可能相关的类似问题:
Can a string literal be subscripted in a constant expression?
User defined literal arguments are not constexpr?
第一个似乎表明至少对于 char const (&amp;str)[N] 这是有效的,并且 GCC 接受它,尽管我承认无法得出结论。
第二个使用整数文字,而不是字符串文字,最后通过使用模板元编程(我不想要)解决了这个问题。那么显然问题不仅限于字符串文字?

【问题讨论】:

  • @R.MartinhoFernandes:感谢std::size_t 指针。因此,如果它适用于 4.7.3 和 4.8,它似乎是 gcc 4.7.2 的限制。事实上,如果我尝试将其用于enum,我会得到“'STRING_USERDEF' 令牌之前的预期标识符”,所以显然我的 GCC 版本没有选择不要在编译时评估,但它根本不能。如果您愿意,请将您的发现添加为答案,我会接受它作为“必须升级编译器”(meh,compile gcc ...)。
  • 好的,我添加了我的发现的答案以及一些关于__builtin_constant_p 的信息。我现在正在删除 cmets,因为它们现在是多余的。
  • @Damon 您可以找到一个有趣的__builtin_constant_p here 用例以及该功能的一些背景。

标签: c++ gcc c++11 constexpr user-defined-literals


【解决方案1】:

我手头没有 GCC 4.7.2 可供尝试,但是没有静态断言的代码(稍后会详细介绍)编译得很好,并在编译时使用 GCC 4.7.3GCC 4.8 执行函数。我猜你将不得不更新你的编译器。

编译器并不总是允许将求值移至运行时:某些上下文,如模板参数和static_assert,需要在编译时求值,否则会出错。如果您在 static_assert 中使用您的 UDL,您将强制编译器在编译时对其进行评估(如果可能)。在我的两个测试中都是这样。

现在,转到__builtin_constant_p(str)。首先,如文档所述,__builtin_constant_p 可能会产生误报(即它有时会为常量表达式返回 0)。

str 不能证明是一个常量表达式,因为它是一个函数参数。在某些上下文中,您可以强制编译器在编译时评估函数,但这并不意味着它可以从不在运行时评估它:某些上下文从不强制编译时评估(事实上,在某些情况下,编译时评估是不可能的)。 str 可以是非常量表达式。

编译器看到函数时会测试静态断言,而不是编译器看到的每个调用都测试一次。这使得您总是在编译时上下文中调用它的事实无关紧要:只有主体很重要。因为str 有时可能是一个非常量表达式,所以__builtin_constant_p(str) 在这种情况下不可能为真:它可以产生假阴性,但不会产生假阳性。

为了更清楚:static_assert(__builtin_constant_p("blah"), "") 会通过(好吧,理论上它可能会失败,但我怀疑编译器会在这里产生假阴性),因为"blah" 总是一个常量表达式,但str"blah" 不是同一个表达式。

为了完整起见,如果所讨论的参数是数字类型(稍后会详细介绍),并且您在静态断言之外进行了测试,如果您通过了,您可以获得the test to return true一个常数,如果你传递了一个非常数,则为 false。在静态断言中,它是always fails

但是! The docs for __builtin_constant_p 透露了一个有趣的细节:

但是,如果您在内联函数中使用它并将函数的参数作为参数传递给内置函数,则当您使用字符串常量或复合字面量调用内联函数时,GCC 永远不会返回 1(请参阅 Compound字面量),并且当您将常量数值传递给内联函数时不会返回 1,除非您指定 -O 选项。

如您所见,如果给定的表达式是字符串常量,则内置函数有一个限制 makes the test always return false

【讨论】:

  • 哇,您挖出的__builtin_constant_p 文档中的注释特别有趣。我根本不知道这个条款。
  • 同时,我升级到了gcc-4.8.1。有趣的是,它仍然似乎不会在编译时进行评估,除非我强制它(通过分配给enum)。有趣的是,打印enum 值,然后使用相同的文字调用constexpr 函数,首先打印编译时常量,然后计算另一个。因此,尽管 gcc 合并了字符串文字,但它似乎并没有“记住”它已经在编译时对相同(嗯,不同,但相同)文字进行了相同的 constexpr 计算.
猜你喜欢
  • 1970-01-01
  • 2021-02-13
  • 2012-12-24
  • 2014-04-06
  • 2017-05-26
  • 2020-10-01
  • 2022-11-18
  • 1970-01-01
  • 2021-09-04
相关资源
最近更新 更多