How to compare string_view using if-constexpt in a constexpr context

2.2k views Asked by At

Is it possible to compare std::string_view using "if constexpr" in a constexpr context? And why is_hello_2 and is_hello_4 fail to compile showing error: "ā€˜sā€™ is not a constant expression"

static constexpr bool is_hello_1(auto s) {
  return s == "hello";
}

static constexpr bool is_hello_2(auto s) {
    if constexpr (s == "hello") {
        return true;
    }
    return false;
}

static constexpr auto is_hello_3 = [](auto s) {
    return s == "hello";
};

static constexpr auto is_hello_4 = [](auto s) {
    if constexpr (s == "hello") {
        return true;
    }
    return false;
};

Considering the main function (https://godbolt.org/z/zEcnb8):

int main(int argc, char **argv) {
    static constexpr const std::string_view s1 ("hello");
    if constexpr (s1 == "hello"){}
    if constexpr (is_hello_1(s1)){}
    // if constexpr (is_hello_2(s1)){} // <- doesn't compile
    if constexpr (is_hello_3(s1)){}
    // if constexpr (is_hello_4(s1)){} // <- doesn't compile
    return 0;
}

Is there a way to fix "is_hello_2" and "is_hello_4"?

3

There are 3 answers

0
KamilCuk On BEST ANSWER

Is there a way to fix "is_hello_2" and "is_hello_4"?

Remove the constexpr from ifs in is_hello_2 or is_hello_4.

How to compare string_view using if-constexpt in a constexpr context

Normally, like anywhere else.

static constexpr bool is_hello_5() {
    constexpr const std::string_view s1 ("hello");
    if constexpr (s1 == "hello") {
        return true;
    }
    return false;
}

Function arguments values are not constant expressions and you can't use them in a if constexpr.

0
Yakk - Adam Nevraumont On

I won't use the formal standard wording, but rather explain the problem.

if constexpr requires that all of its arguments are always constexpr.

Arguments to constexpr functions are sometimes constexpr.

You can call constexpr functions with non-constexpr arguments.

Try consteval.

#include <string_view>

using namespace std::literals;

consteval bool is_hello_1(auto s) {
  return s == "hello";
}

consteval bool is_hello_2(auto s) {
    if (s == "hello") {
        return true;
    }
    return false;
}


int main(int argc, char **argv) {

    static constexpr std::string_view s1 ("hello");
    static_assert(s1 == "hello");
    static_assert(is_hello_1(s1));
    static_assert(is_hello_2("hello"sv));
    return 0;
}

Live example

You can put consteval in a lambda where you'd usually put mutable (I didn't recall this off-hand, so I didn't include lambdas in my sample code above).

Finally, I'd advise using anonymous namespaces to static functions local to C++ files, and use neither in headers.


This might not do everything you want; the advantage of if constexpr is you can do type-invalid things based on the branch. And C++ doesn't let you do type-invalid things based on the value of a function argument; if it did, the compiler couldn't compile the body of a function without its argument values being supplied.

To get around that you can do something like create a compile-time string.

template<auto s>
consteval bool is_hello_2() {
  if constexpr (s == "hello") {
    return true;
  }
  return false;
}

and call it with

template<std::size_t N>
struct compile_time_string : std::array<char, N+1> {
    constexpr std::array<char, N+1>& buffer() { return *this; }
    constexpr std::array<char, N+1> const& buffer() const { return *this; }
    constexpr std::string_view view() const { return {this->data(), this->data()+this->size()}; }
    
private:
    template<std::size_t...Is>
    constexpr compile_time_string( char const* str, std::index_sequence<Is...> ):
        std::array<char, N+1>{{ str[Is]..., char(0) }}
    {}
public:
    explicit constexpr compile_time_string( char const* str ):
        compile_time_string( str, std::make_index_sequence<N>{} )
    {}
    explicit constexpr compile_time_string( std::array<char, N+1> buff ) :
        std::array<char, N+1>(buff)
    {}

    constexpr compile_time_string( compile_time_string const& ) = default;
    compile_time_string() = delete;
    
        
    constexpr auto operator<=>( compile_time_string const& o ) const = default;
    constexpr bool operator==( compile_time_string const& o ) const = default;
    
    template<std::size_t N_arg>
    friend constexpr auto operator==( char const(&lhs)[N_arg], compile_time_string const& rhs )
    {
        return std::string_view{ lhs, lhs+N_arg } == rhs.view();
    }
    template<std::size_t N_arg>
    friend constexpr auto operator==( compile_time_string const& lhs, char const(&rhs)[N_arg] )
    {
        return lhs.view() == std::string_view{ rhs, rhs+N_arg };
    }
};
template<std::size_t N>
compile_time_string( char const(&)[N] )->compile_time_string<N-1>;


template<auto s>
consteval bool is_hello_3() {
    if (s == "hello") {
        return true;
    }
    return false;
}


static_assert(is_hello_3<compile_time_string("hello")>());

Live example.

I pared down the compile time string a bunch. You'll want better <=> and == and the like for more types.

Also, I think in C++20 you can make "stronger" compile time strings where the string actually lives in an auto argument. But I'm uncertain.

0
Vasilij On

Think from the function perspective (as if you are inside the function scope): how can it parse your code in compile time if function arguments are unknown? That is the same reason you mark s1 as constexpr in the main function. If you don't - the code won't compile.

So what you have to do is to remove constexpr from is_hello2 and is_hello4. This doesn't prevent the function from being used in constexpr.

#include <string_view>

static constexpr bool is_hello_1(auto s) {
  return s == "hello";
}

static constexpr bool is_hello_2(auto s) {
    if (s == "hello") {
        return true;
    }
    return false;
}

static constexpr auto is_hello_3 = [](auto s) {
    return s == "hello";
};

static constexpr auto is_hello_4 = [](auto s) {
    if (s == "hello") {
        return true;
    }
    return false;
};

int main(int argc, char **argv) {

    static constexpr std::string_view s1 ("hello");
    static_assert(s1 == "hello");
    static_assert(is_hello_1(s1));
    static_assert(is_hello_2("hello"));
    static_assert(is_hello_3(s1));
    static_assert(is_hello_4(s1));
    return 0;
}

But it is perfectly fine to use if constexpr on type and none-type template parameters:

template<int s>
static constexpr auto is_5 = []() {
    if constexpr (s == 5) {
        return true;
    }
    return false;
};

static_assert(is_5<5>());

Unfortunately you can't have std::string_view as a non-type template parameter for now.