const char* cannot be used as a constant value for std::char_traits<char>::length

Viewed 304

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

2 Answers

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

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.

Related