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.