Dev Null
Dev Null

Reputation: 4997

How to static assert a condition on a string literal inside of a constexpr function?

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

Example.

Upvotes: 9

Views: 5920

Answers (3)

Julius
Julius

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

WorldSEnder
WorldSEnder

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

max66
max66

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

Related Questions