8

The CTRE library is able to parse and validate regular expressions at compile time using syntax like ctre::match<"REGEX">(text_to_search). I know this syntax is only supported in C++20, which is fine, but I am unable use string literals this way no matter what I try. Here's a very simple example:

// The compiler refuses to pass string literals to STR in this compile time version.
template <char const STR[2]> constexpr int to_int_compile_time()
{
    return STR[0] - '0';
}

// It has no problems passing the string literal to str in this version.
int to_int_runtime(char const str[2])
{
    return str[0] - '0';
}

Calling to_int_runtime("0") works fine, but to_int_compile_time<"0">() complains that string literals can't be used for this template parameter. How should to_int_compile_time be written so that the string literal can be passed into the char array template paramter?

0

2 Answers 2

13

Being able to do this hinges on a little-known feature of C++20: a non-type template parameter can have a class template type, without template arguments specified. CTAD will determine those arguments.

So you create a class templated by size_t N, that has char[N] as a member, is constructible from one, and N is deducible by CTAD.

Example:

// This does nothing, but causes an error when called from a `consteval` function.
inline void expectedNullTerminatedArray() {}

template <std::size_t N>
struct ConstString
{
    char str[N]{};

    static constexpr std::size_t size = N - 1;

    [[nodiscard]] constexpr std::string_view view() const
    {
        return {str, str + size};
    }

    consteval ConstString() {}
    consteval ConstString(const char (&new_str)[N])
    {
        if (new_str[N-1] != '\0')
            expectedNullTerminatedArray();
        std::copy_n(new_str, size, str);
    }
};

Then you do template <ConstString S> struct A {...};, and use either S.str or S.view() to examine the string.

And here are some extra convenience operators for this class:

template <std::size_t A, std::size_t B>
[[nodiscard]] constexpr ConstString<A + B - 1> operator+(const ConstString<A> &a, const ConstString<B> &b)
{
    ConstString<A + B - 1> ret;
    std::copy_n(a.str, a.size, ret.str);
    std::copy_n(b.str, b.size, ret.str + a.size);
    return ret;
}

template <std::size_t A, std::size_t B>
[[nodiscard]] constexpr ConstString<A + B - 1> operator+(const ConstString<A> &a, const char (&b)[B])
{
    return a + ConstString<B>(b);
}

template <std::size_t A, std::size_t B>
[[nodiscard]] constexpr ConstString<A + B - 1> operator+(const char (&a)[A], const ConstString<B> &b)
{
    return ConstString<A>(a) + b;
}

You can also have template UDLs with this class:

template <ConstString S>
struct ConstStringParam {};

template <ConstString S>
[[nodiscard]] constexpr ConstStringParam<S> operator""_c()
{
    return {};
}

// -----

template <ConstString S> void foo(ConstStringParam<S>) {}

foo("Sup!"_c);
Sign up to request clarification or add additional context in comments.

8 Comments

I've tried doing similar things, but I can't find a way to access the characters in the string in compile time expressions. For example, I replaced ASSERT(new_str[N-1] == '\0'); with static_assert, since that's where I'm trying to go with this, and the compiler does not consider new_str[N-1] a valid constexpr expression.
@BobBuilder You don't need a static one. The standard assert() will work, because on the happy path it's constexpr. I've edited the answer to not confuse anyone.
Are you referring to assert() from assert.h? I tried using that one, and it compiles but does not check anything at compile time. As a simple test, I put in assert(new_str[0] == something_that_gives_a_false_result), and it still compiles without errors.
@BobBuilder Yes, but I just figured out a better test, see the edit.
Thank you, that last edit achieves compile time string validation. It feels a bit like a hack, but it's the only thing I've seen that actually solves the problem. :)
|
1
template <char const STR[2]> constexpr int to_int_compile_time()
{
    return STR[0] - '0';
}

This function cannot be used with string literals. Firstly, note that ([temp.param] p10):

A non-type template-parameter of type “array of T” or of function type T is adjusted to be of type “pointer to T”.

You are effectively writing template <char const*>. In template arguments, pointers are not allowed to point to string literal objects ([temp.arg.nontype] p6.2), which is why your code is rejected.

As a workaround, you can create your own compile-time string class, as suggested in the accepted answer.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.