Computing length of a C string at compile time. Is this really a constexpr?

36,408

Solution 1

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.

Solution 2

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;
}

Solution 3

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.

Solution 4

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

Solution 5

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.

Share:
36,408
Mircea Ispas
Author by

Mircea Ispas

Updated on September 29, 2021

Comments

  • Mircea Ispas
    Mircea Ispas over 2 years

    I'm trying to compute the length of a string literal at compile time. To do so I'm using following code:

    #include <cstdio>
    
    int constexpr length(const char* str)
    {
        return *str ? 1 + length(str + 1) : 0;
    }
    
    int main()
    {
        printf("%d %d", length("abcd"), length("abcdefgh"));
    }
    

    Everything works as expected, the program prints 4 and 8. The assembly code generated by clang shows that the results are computed at compile time:

    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
    

    My question: is it guaranteed by the standard that length function will be evaluated compile time?

    If this is true the door for compile time string literals computations just opened for me... for example I can compute hashes at compile time and many more...

  • BЈовић
    BЈовић over 9 years
    ... and compile with -pedantic, if you use gcc. Otherwise, you get no warnings and errors
  • Angew is no longer proud of SO
    Angew is no longer proud of SO over 9 years
    @BЈовић Or use it in a context where GCC has no extensions potentially getting in the way, such as a template argument.
  • sharptooth
    sharptooth over 9 years
    Wouldn\t an enum hack be more reliable? Such as enum { Whatever = length("str") } ?
  • chris
    chris over 9 years
    Worthy of mention is static_assert(length("str") == 3, "");
  • Ben Voigt
    Ben Voigt over 9 years
    @sharptooth: Or a template argument. Or a constexpr initializer. Or a switch case. Yes, I guess array size isn't good for testing with g++ because it supports C99-style VLAs.
  • BЈовић
    BЈовић over 9 years
    @Angew that would be another answer (IMO better then this, as this require extensions to be disabled)
  • Mircea Ispas
    Mircea Ispas over 9 years
    or just use std::integral_constant<int, length(...)>::value
  • chris
    chris over 9 years
    The example is a bit of a pointless use since len being constexpr means length must be evaluated at compile time anyway.
  • T.C.
    T.C. over 9 years
    constexpr auto test = /*...*/; is probably the most general and straightforward.
  • Ben Voigt
    Ben Voigt over 9 years
    These conditions do not guarantee the returned value is constant. For example, the function might be called with other argument values.
  • 5gon12eder
    5gon12eder over 9 years
    @chris I didn't know it must be, though I have observed that it is with my compiler.
  • kaedinger
    kaedinger over 9 years
    Right, @BenVoigt. I edited it to be called with a constant expression.
  • Steve Jessop
    Steve Jessop over 9 years
    That said, in principle a compiler is entitled to see an object with internal or no linkage with constexpr int x = 5;, observe that it doesn't require the value at compile-time (assuming it isn't used as a template parameter or whatnot) and actually emit code that computes the initial value at runtime using 5 immediate values of 1 and 4 addition ops. A more realistic example: the compiler might hit a recursion limit and defer computation until runtime. Unless you do something that forces the compiler to actually use the value, "guaranteed to be evaluated at compile time" is a QOI issue.
  • Shafik Yaghmour
    Shafik Yaghmour over 9 years
    @SteveJessop Bjarne seems to be using a concept which does not have an analogue I can find in the draft standard which is used as a constant expression means evaluated at translation. So it would seem that the standard does not explicitly state what he is saying, so I would tend to agree with you. Although both Bjarne and Herb seem to agree on this, which could indicate it is just underspecified.
  • Steve Jessop
    Steve Jessop over 9 years
    I think they're both considering only "self-respecting compilers", as opposed to the standards-conforming but wilfully obstructive compiler I hypothesise. It's useful as a means of reasoning about what the standard actually guarantees, and not much else ;-)
  • 5gon12eder
    5gon12eder over 9 years
    Ok, according to the majority of other answers it has to, so I have modified the example to be less pointless. In fact, it was an if-condition (where it was essential the compiler did dead code elimination) for which I originally used the trick.
  • Angew is no longer proud of SO
    Angew is no longer proud of SO over 9 years
    @SteveJessop Wilfully obstructive compilers, like the infamous (and unfortunately nonexistent) Hell++. Such a thing would actually be great for testing conformance/portability.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 9 years
    Under the as-if rule, even using the value as a seeming compile time constant is not enough: the compiler is free to ship a copy of your source and recompile it at runtime, or do ru ti e calculation to determine the type of a variable, or just pointlessly rerun your constexpr calculation out of sheer evil. It is even free to wait 1 second per character in a given line of source, or take a given line of source and use it to seed a chess position, then play both sides to determine who won.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 9 years
    @T.C. template<class T,T value>using compile_time=value; #define COMPILE_TIME(...) compile_time<decltype(__VA_ARGS__),__VA_ARGS__> might work as well, and not require a separate line.
  • Shafik Yaghmour
    Shafik Yaghmour over 9 years
    @Yakk as far as I can tell that is basically what Steve said above and I agreed in my comment the standard as written does not seem to support their interpretation. Two possible conclusions is that they are as Steve said only referring to self-respecting compilers or it is underspecified and the committees intention was as stated by Bjarne and Herb and this will be fixed in later drafts. I unfortunately can not tell you which it is, although I consider them to authoritative sources and practically their answers reflects implementations as they exist now.
  • Shafik Yaghmour
    Shafik Yaghmour over 9 years
    Note, this works because strlen is a built-in function, if we use -fno-builtins it reverts to calling it at run-time, see it live
  • Aaron McDaid
    Aaron McDaid almost 9 years
    strlen is constexpr for me, even with -fno-nonansi-builtins (seems like -fno-builtins doesn't exist in g++ any more). I say "constexpr", because I can do this template<int> void foo(); and foo<strlen("hi")>(); g++-4.8.4
  • unkulunkulu
    unkulunkulu about 8 years
    It can be not equal to strlen due to embedded '\0': strlen("hi\0there") != length("hi\0there")
  • QuantumKarl
    QuantumKarl about 7 years
    This is the correct way, this is an example in Effective Modern C++ (if I recall correctly). However, there is a nice string class that is entirely constexpr see this answer: Scott Schurr's str_const, perhaps this will be more useful (and less C style).
  • supercat
    supercat about 7 years
    @ShafikYaghmour: If the code required to compute a value of a large type (e.g. "complex") would be smaller and faster than the code to load a constant, a compiler wouldn't have to be evil to compute a value at runtime. For the Standard to say that a compiler should compute constant expressions at compile time except when it would be more efficient to compute them at runtime would likely have been seen as condescending, though perhaps it would be helpful for standards to include something analogous to the U.S. Constitution's Ninth Amendment: the failure of the Standard to forbid something...
  • supercat
    supercat about 7 years
    ...does not, in and of itself, imply that the action would be reasonable. The Standard makes no attempt to forbid every possible unreasonable way in which implementations might behave, and quality implementations should not require programmers to anticipate all possible forms of unreasonable behavior not anticipated by the Committee.
  • QuantumKarl
    QuantumKarl about 7 years
    @MikeWeir Ops, that is odd. Here are various links: link to question, link to paper, link to source on git
  • Pablo Ariel
    Pablo Ariel almost 5 years
    now yow do : char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
  • Bulletmagnet
    Bulletmagnet about 2 years
    @ShafikYaghmour strlen is not a built-in function, it's a function defined by the standard C library, and there's no constexpr in the C language.
  • kkaja123
    kkaja123 almost 2 years
    @PabloAriel Using sprintf() is not constexpr, so the fact that length() does not return the expected string length is not relevant for this question. Additionally, the char array is not constexpr, so there wouldn't be any point to getting its size at compile time if the string it contains can be of variable length at run time (unless you're using it to measure the max character capacity).