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.