2

I have the following code:

constexpr uint32_t countWords(const char* str) {
    constexpr std::size_t length = std::char_traits<char>::length(str);
    std::uint32_t count = 0;
    for (std::size_t i = 0; i < length; i++) {
        if (str[i] == ' ') {
            count++;
        }
    }
    return count;
}

My problem arises on the first line of the function where I get a syntax error stating that:

str cannot be used as a constant

when I try to pass it to std::char_traits<char>::length. If I remove the constexpr from the length variable the error goes away, but to me that implies that the variable is not obtainable at compile time which defeats the purpose of the constexpr function. I plant call this functions using string literals as the parameter.

std::char_traits::length

NutCracker
  • 11,485
  • 4
  • 44
  • 68
Ryoku
  • 397
  • 2
  • 16
  • 2
    All the program have is the pointer `str`. There's no way to know what that pointer is really pointing to. Especially, it can't be known at compile.time. *However* if the string passed to `length` *is* known at compile-time (and the compiler really can deduce it, which is not possible in this case) then the length could be calculated at compile-time. Remember that `constexpr` functions also have to work with run-time data. – Some programmer dude Jan 16 '21 at 08:01
  • @Someprogrammerdude there was an answer provided for a question of ["how to evaluate string length at compile time"](https://stackoverflow.com/a/51187953/9211144) that suggested to use `std::char_traits::length` and as of C++17 the `std::char_traits::length` function is `constexpr`. I am trying to pass a string literal into the `countWords` function, is there a way to make that evident to the compiler? – Ryoku Jan 16 '21 at 08:04
  • 1
    @Ryoku `const char* str` is `const` but not `constexpr`. – πάντα ῥεῖ Jan 16 '21 at 08:14
  • 1
    @Ryoku If you pass in a string literal, the compiler can run the function at compile-time. You don't need `length` to be `constexpr`. – super Jan 16 '21 at 08:15
  • @πάνταῥεῖ constexpr is an invalid specifier when used as a parameter – Ryoku Jan 16 '21 at 08:16
  • @super but as far as I am aware by enforcing `length` to be `constexpr` it all but guarantees that the compiler will evaluate it at compile time. More to the point, why is it not valid to make `length` `constexpr` – Ryoku Jan 16 '21 at 08:17
  • @Ryoku Yes exactly, that's why that cant ever work. – πάντα ῥεῖ Jan 16 '21 at 08:17
  • Does this answer your question? [Why a std::array is not constant expression when it is the input of a templated function/generic lambda?](https://stackoverflow.com/questions/65236583/why-a-stdarray-is-not-constant-expression-when-it-is-the-input-of-a-templated) – super Jan 16 '21 at 08:19
  • @Ryoku It's not valid to make it `constexpr` because the calculated value depends on `str` which is not a compile time constant. – super Jan 16 '21 at 08:20
  • @Ryoku _"why is it not valid to make length constexpr"_ because your whole function isn't `constexpr`. How can the compiler know at compie time, what will be passed as a parameter at runtime?l – πάντα ῥεῖ Jan 16 '21 at 08:20
  • I assumed that as long as I was using literals it would have everything that it required at compile time. I can see now that `str` is not a compile time constant, but I see many solutions on stack overflow to calculate the length of a string at compile time ([such as this](https://stackoverflow.com/a/15863804/9211144), since most of these solutions rely on a const char* parameter is it safe to assume that they simply evaluate at run time – Ryoku Jan 16 '21 at 08:27
  • If you want to guarantee that the function is evaluated at compile time, you can use `constexpr` at the calling site. `constexpr auto wordcount = countWords("This is words");`. Possible after removing `constexpr` from length. – super Jan 16 '21 at 08:29

3 Answers3

3

From the comments it seems you are interested in using this on string literals. All you need to do to make that work is to remove constexpr from length.

The function has to be callable at run-time as well as compile-time.

But when you call it with a string literal it can be calculated at compile time. You can verify this by assigning the return value of the function to a constexpr variable.

#include <iostream>
#include <string>

constexpr uint32_t countWords(const char* str) {
    std::size_t length = std::char_traits<char>::length(str);
    std::uint32_t count = 0;
    for (std::size_t i = 0; i < length; i++) {
        if (str[i] == ' ') {
            count++;
        }
    }
    return count;
}

int main()
{
    constexpr auto wordcount = countWords("This is a sentence");
    std::cout << wordcount;
}
super
  • 12,335
  • 2
  • 19
  • 29
  • 1
    I didn't think there was such as simple way to check if a function is guaranteed to be evaluated at compile time. I saw some solutions on stackoverflow before but they often involved strange macros [such as this](https://stackoverflow.com/a/46920091/9211144). Your solution seems to rely on the satisfaction of the 3rd requirement of constexpr variables: _the full-expression of its initialization, including all implicit conversions, constructors calls, etc, must be a constant expression_, which makes sense to me – Ryoku Jan 16 '21 at 08:44
1

First, you need to make your compiler know that the length will be calculated at compile-time. With your current implementation, str parameter could be passed to the function call at both compile time and runtime (if you didn't know, constexpr is not forced to be compile time, it can be executed at runtime too; check for consteval from C++20 which forces compile time calculation).

So, in order to make sure your str variable is passed at compile time, you may want to pass it as non-type template parameter like:

template <char const * S>
constexpr uint32_t countWords() {
    constexpr std::size_t length = std::char_traits<char>::length(S);
    std::uint32_t count = 0;
    for (std::size_t i = 0; i < length; i++) {
        if (S[i] == ' ') {
            count++;
        }
    }
    return count;
}

but, please note, that this will work only if your S pointer has static storage, so following would work:

static constexpr char str[]{ "a b c" };
constexpr auto cnt = countWords<str>();

but following would NOT work:

constexpr char str[]{ "a b c" };
constexpr auto cnt = countWords<str>();  // ERROR

For more info, please refer to this question here.

Apart of this, your countWords function does not do the right thing because the above example will set variable cnt to value 2 which is not right.

EDIT:

If you want to use function on string literals, then the other answer describes the fix.

NutCracker
  • 11,485
  • 4
  • 44
  • 68
  • 1
    Very interesting work around, unfortunately it prevents the use of string literals but at least it can be worked around using static char arrays like you showed. I'm not sure why `count` will evaluate to 2 but I should be able to figure it out – Ryoku Jan 16 '21 at 08:29
  • 1
    @Ryoku For fixing your counting part of your function, please refer to this question [here](https://stackoverflow.com/questions/3672234/c-function-to-count-all-the-words-in-a-string). And don't forget to upvote if you liked it:) – NutCracker Jan 16 '21 at 08:32
  • 1
    Thanks for mentioning it in your answer for others, but yeah I knew about consteval and I would use it but I am programming using visual studio and using the newest c++ messes with the intellisense which I am not a fan of. So long as the other answer guarantees compile time evaluation by setting `constexpr` to the variable receiving the return value I think I will use that. Thank you for the reference you provided for calculating a more accurate word count – Ryoku Jan 16 '21 at 08:52
0

Another solution is to avoid std::char_traits::length and use old, good template that will give you array size at compile time:

constexpr const char words[] = "SOME WORDS AND MORE";

template <size_t length>
constexpr size_t countWords(const char (&str)[length]) {
    size_t count = 1;
    for (std::size_t i = 0; i < length; i++) {
        if (str[i] == ' ') {
            count++;
        }
    }
    return count;
}

int main() {
    constexpr auto words = countWords(words);
    printf("%d: ", words); // 4
}

ps. Note about difference between array size and string size. Array of chars will include zero terminator. pss. Actually it's more like countSpaces function :)

crea7or
  • 4,421
  • 2
  • 26
  • 37