Passing a string literal to a template char array parameter [duplicate]
Asked Answered
E

2

7

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?

Ecclesiolatry answered 17/6, 2021 at 18:12 Comment(0)
P
11

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);
Pelite answered 17/6, 2021 at 18:19 Comment(8)
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.Ecclesiolatry
@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.Pelite
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.Ecclesiolatry
@BobBuilder Yes, but I just figured out a better test, see the edit.Pelite
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. :)Ecclesiolatry
@BobBuilder: Everyone wants to just have the string literal be the template argument, but string literals are very old and very special and making them more regular is much harder than it sounds.Wurst
But the standard says a literal class type with the following properties: all base classes and non-static data members are public and non-mutable and [7.3.1]. Aren't the str array field mutable?Amundsen
@Amundsen They likely mean the mutable keyword.Pelite
S
0
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.

Seeto answered 27/6 at 9:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.