2

尝试通过用户定义的文字实现一个令人愉悦的(简单、直接、没有 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""也是 a constexpr

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

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

可能相关的类似问题:
字符串文字可以在常量表达式中下标吗?
用户定义的文字参数不是 constexpr?
第一个似乎表明至少对于char const (&str)[N]这项工作,GCC 接受它,尽管我承认无法得出结论。
第二个使用整数文字,而不是字符串文字,最后通过使用模板元编程(我不想要)解决了这个问题。那么显然问题不仅限于字符串文字吗?

4

1 回答 1

3

I don't have GCC 4.7.2 at hand to try, but your code without the static assertion (more on that later) compiles fine and executes the function at compile-time with both GCC 4.7.3 and GCC 4.8. I guess you will have to update your compiler.

The compiler is not always allowed to move the evaluation to runtime: some contexts, like template arguments, and static_assert, require evaluation at compile-time or an error if not possible. If you use your UDL in a static_assert you will force the compiler to evaluate it at compile-time if possible. In both my tests it does so.

Now, on to __builtin_constant_p(str). To start with, as documented, __builtin_constant_p can produce false negatives (i.e. it can return 0 for constant expressions sometimes).

str is not provably a constant expression because it is a function argument. You can force the compiler to evaluate the function at compile-time in some contexts, but that doesn't mean it can never evaluate it at runtime: some contexts never force compile-time evaluation (and in fact, in some of those contexts compile-time evaluation is just impossible). str can be a non-constant expression.

The static assertions are tested when the compiler sees the function, not once for each call the compiler sees. That makes the fact that you always call it in compile-time contexts irrelevant: only the body matters. Because str can sometimes be a non-constant expression, __builtin_constant_p(str) in that context cannot be true: it can produce false negatives, but it does not produce false positives.

To make it more clear: static_assert(__builtin_constant_p("blah"), "") will pass (well, in theory it could be fail, but I doubt the compiler would produce a false negative here), because "blah" is always a constant expression, but str is not the same expression as "blah".

For completeness, if the argument in question was of a numeric type (more on that later), and you did the test outside of a static assertion, you could get the test to return true if you passed a constant, and false if you passed a non-constant. In a static assertion, it always fails.

But! The docs for __builtin_constant_p reveal one interesting detail:

However, if you use it in an inlined function and pass an argument of the function as the argument to the built-in, GCC will never return 1 when you call the inline function with a string constant or compound literal (see Compound Literals) and will not return 1 when you pass a constant numeric value to the inline function unless you specify the -O option.

As you can see the built-in has a limitation makes the test always return false if the expression given is a string constant.

于 2013-06-21T13:28:47.433 回答