Determine if string contains a particular character at compile time

Viewed 146

I'd like to determine if a string contains a particular character at compile time. I thought I would use std::string_view as it has constexpr methods to do what I want. This is the code:

#include <iostream>
#include <string_view>

using namespace std::literals;

template<std::size_t N>
constexpr bool ContainsAsterisk(const char(&formatString)[N])
{
    constexpr std::string_view fmtString{ formatString, N - 1 }; // error C2131: expression did not evaluate to a constant
    constexpr bool containsAsterisk = fmtString.find('*') != fmtString.npos;
    return containsAsterisk;
}


int main()
{
    if (ContainsAsterisk("sdf"))
    {
        std::cout << "sdf no\n";
    }

    if (ContainsAsterisk("er*r"))
    {
        std::cout << "er*r yes\n";
    }


    std::cout << "done\n";
}

This doesn't compile because of these errors

ConsoleApplication.cpp(9,41): error C2131: expression did not evaluate to a constant
ConsoleApplication.cpp(9,43): message : failure was caused by a read of a variable outside its lifetime
ConsoleApplication.cpp(9,43): message : see usage of 'formatString'
ConsoleApplication.cpp(17): message : see reference to function template instantiation 'bool ContainsAsterisk<4>(const char (&)[4])' being compiled
ConsoleApplication.cpp(10,37): error C2131: expression did not evaluate to a constant
ConsoleApplication.cpp(9,43): message : failure was caused by a read of a variable outside its lifetime
ConsoleApplication.cpp(9,43): message : see usage of 'formatString'

I've done quite a bit of googling, and can't understand what this error is telling me! I don't understand how the variable is being read outside it's lifetime, it's a literal (isn't it?) that I thought would be available at compile time.

Any tips explaining the error and how to fix would be appreciated. Thanks.

3 Answers

You are overcomplicating things. std::string_view can be constructed by const char*:

constexpr bool ContainsAsterisk(std::string_view view) {
    // constexpr bool b = view.find('*') != view.npos; // ERROR
    return view.find('*') != view.npos;
}

int main() {
  constexpr bool b = ContainsAsterisk("123123*"); // OK
}

Why am I getting the error?

According to cppreference, a function can be constexpr if:

there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression [...]

This means that it's not necessary for a constexpr function to always return a constexpr value, neither is it expected to always receive a constexpr argument. It only makes sure that for some specific set of arguments (a constexpr const char* in thie case), it will give a constexpr return value.

Therefore, a definition that assumes the argument is always constexpr (see ERROR line above) is invalid.

Your code can simply be written as:

template<std::size_t N>
constexpr bool ContainsAsterisk(const char(&formatString)[N])
{
    for (auto c : formatString)
    {
        if (c == '*')
        {
            return true;
        }
    }
    return false;
}

You don't actually need to use string_view.

Unfortunately I can't explain to you why string_view doesn't work.

You already have answers on how else to do this so I wont add anything more to that but this answer will address

I don't understand how the variable is being read outside it's lifetime, it's a literal (isn't it?) that I thought would be available at compile time.

Yes, a string literal is a compile time constant. The issue here is that a function parameter is not a compile time constant, even if it is initialized from one. To see why that is, lets start with the function

template <auto var>
auto foo()
{
    if constexpr (var == 1)
        return 1;
    else
        return 1.0;
}

This function template will have two different return types, but only one of them will ever be used in a specialization so you still follow the rule of one return type per function. Now if we allowed constexpr function parameters like

auto foo(constexpr int var)
{
    if constexpr (var == 1)
        return 1;
    else
        return 1.0;
}

This function will either return a int or a double, but you can't have one function that has different return types.

Related