Reputation: 4997
In the following example main
can static_assert
if a string literal starts with 'v'
, but verify
can not.
Why is it happening? Is there a way to allow verify
to static_assert
conditions on characters in a string literal?
#include <cstddef>
template <std::size_t N>
constexpr char get_first(const char (&str)[N])
{
static_assert(N>1, "must be > 1");
return str[0];
}
template <std::size_t N>
constexpr void verify(const char (&str)[N])
{
static_assert(str[0] == 'v', "must start from v");
}
int main()
{
static_assert(get_first("value") == 'v', "first must be 'v'"); // succeeds
verify("value"); // fails to compile
}
Compilation errors:
main.cpp: In instantiation of 'constexpr void verify(const char (&)[N]) [with long unsigned int N = 6]':
main.cpp:19:15: required from here
main.cpp:13:9: error: non-constant condition for static assertion
static_assert(str[0] == 'v', "must start from v");
^~~~~~~~~~~~~
main.cpp:13:9: error: 'str' is not a constant expression
Upvotes: 9
Views: 5920
Reputation: 1861
In C++17 you can wrap the value in a constexpr lambda (online demo). The call looks like
verify([=] { return "value"; });
and to unwrap you can
template <class StringWrapper>
constexpr void verify(StringWrapper str_w)
{
constexpr auto str = str_w();
static_assert(str[0] == 'v', "must start from v");
}
Upvotes: 3
Reputation: 5044
I have another workaround for you. This will not use static_assert
but is guaranteed to enforce the condition on compile time.
#include <type_traits>
template<bool b>
using enforce = std::bool_constant<b>;
template <std::size_t N>
constexpr int verify(const char (&str)[N])
{
if(get_first(str) != 'v') {
throw "must start from v";
}
return 0;
}
int main()
{
using assertion = enforce<verify("value")>; // compiles
using assertion = enforce<verify("fail")>; // fails to compile
// or use it like
constexpr auto assertion0 = verify("value"); // compiles
}
This uses that throwing is not valid in a constexpr context. The error you'll receive will look somethign like this:
26 : <source>:26:31: error: non-type template argument is not a constant expression
using assertion = enforce<verify("fail")>; // fails to compile
^~~~~~~~~~~~~~
15 : <source>:15:9: note: subexpression not valid in a constant expression
throw "must start from v";
^
26 : <source>:26:31: note: in call to 'verify("fail")'
using assertion = enforce<verify("fail")>; // fails to compile
We can enforce constexpr evaluation of verify
by using it as a template argument. That's also the reason for declaring a non-void return type.
Upvotes: 3
Reputation: 66240
The problem is that verify()
template <std::size_t N>
constexpr void verify(const char (&str)[N])
{
static_assert(str[0] == 'v', "must start from v");
}
can be called compile-time and run-time.
So the error, because can perform a static_assert()
, over str
, when called compile-time but can't when called runtime (when the first char of str
isn't known compile-time).
-- EDIT --
The OP ask for a compile-time check over a string literal.
The following is a silly example and I suppose isn't really useful, but I hope can be of inspiration
template <char const * const str>
constexpr bool startWithUpperLetter ()
{
static_assert( str[0] != 'v', "no start with \'v\', please" );
return (str[0] >= 'A') && (str[0] <= 'Z');
}
constexpr char const str1[] { "ABC" };
constexpr char const str2[] { "abc" };
constexpr char const str3[] { "vwx" };
int main ()
{
static_assert( startWithUpperLetter<str1>() == true, "!" );
static_assert( startWithUpperLetter<str2>() == false, "!" );
// static_assert( startWithUpperLetter<str3>() == false, "!" ); // error
}
Upvotes: 2