在编译时计算 C 字符串的长度?

我试图在编译时计算字符串的长度:

#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函数在编译时被计算的标准是否保证了这一点?

如果这是真的,编译时字符串文字计算的大门刚刚为我打开... 例如,我可以在编译时计算散列和更多..。

41056 次浏览

It's really easy to find out whether a call to a constexpr function results in a core constant expression or is merely being optimized:

Use it in a context where a constant expression is required.

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

A short explanation from Wikipedia's entry on Generalized constant expressions:

The use of constexpr on a function imposes some limitations on what that function can do. First, the function must have a non-void return type. Second, the function body cannot declare variables or define new types. Third, the body may contain only declarations, null statements and a single return statement. There must exist argument values such that, after argument substitution, the expression in the return statement produces a constant expression.

Having the constexpr keyword before a function definition instructs the compiler to check if these limitations are met. If yes, and the function is called with a constant, the returned value is guaranteed to be constant and thus can be used anywhere a constant expression is required.

There is no guarantee that a constexpr function is evaluated at compile-time, though any reasonable compiler will do it at appropriate optimization levels enabled. On the other hand, template parameters must be evaluated at compile-time.

I used the following trick to force evaluation at compile time. Unfortunately it only works with integral values (ie not with floating point values).

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

Now, if you write

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

you can be sure that the if statement is a compile-time constant with no run-time overhead.

Constant expressions are not guaranteed to be evaluated at compile time, we only have a non-normative quote from draft C++ standard section 5.19 Constant expressions that says this though:

[...]>[ Note: Constant expressions can be evaluated during translation.—end note ]

You can assign the result to constexpr variable to be sure it is evaluated at compile time, we can see this from Bjarne Stroustrup's C++11 reference which says (emphasis mine):

In addition to be able to evaluate expressions at compile time, we want to be able to require expressions to be evaluated at compile time; constexpr in front of a variable definition does that (and implies const):

For example:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup gives a summary of when we can assure compile time evaluation in this isocpp blog entry and says:

[...]The correct answer - as stated by Herb - is that according to the standard a constexpr function may be evaluated at compiler time or run time unless it is used as a constant expression, in which case it must be evaluated at compile-time. To guarantee compile-time evaluation, we must either use it where a constant expression is required (e.g., as an array bound or as a case label) or use it to initialize a constexpr. I would hope that no self-respecting compiler would miss the optimization opportunity to do what I originally said: "A constexpr function is evaluated at compile time if all its arguments are constant expressions."

So this outlines two cases where it should be evaluated at compile time:

  1. Use it where a constant expression is required, this would seem to be anywhere in the draft standard where the phrase shall be ... converted constant expression or shall be ... constant expression is used, such as an array bound.
  2. Use it to initialize a constexpr as I outline above.

Just a note, that modern compilers (like gcc-4.x) do strlen for string literals at compile time because it is normally defined as an intrinsic function. With no optimizations enabled. Although the result is not a compile time constant.

E.g.:

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

Results in:

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

Let me propose another function that computes the length of a string at compile time without being recursive.

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

Have a look at this sample code at ideone.

very simple:

sizeof("myStringLiteral") does the work.

the sizeof intrinsic compiler function evaluates guaranteed on compile time. It is a powerful compiler feature, often underestimated. It works on C++ and also C.

Note: You may need to convert from size_t to int, and subtract 1, both also done on compile time:

int test_sizeof_text = (int)(sizeof("1234567")-1);

sizeof("text") is the size inclusively the terminating 0, hence -1 for the number of character.

Starting with C++20 you can use consteval instead of constexpr to enforce compile time execution of a function.