将字符串字面值传递给模板字符数组参数

6

CTRE库可以使用诸如ctre::match<"REGEX">(text_to_search)的语法在编译时解析和验证正则表达式。我知道这种语法只在C++20中支持,这很好,但无论我尝试什么,我都无法以这种方式使用字符串字面量。以下是一个非常简单的示例:

// 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';
}

调用 to_int_runtime("0") 没问题,但是 to_int_compile_time<"0">() 报错,说无法将字符串字面值用于此模板参数。为了能够将字符串字面值传递给字符数组模板参数,应该如何编写 to_int_compile_time

1个回答

10

能够做到这一点取决于C++20中鲜为人知的一个特性:非类型模板参数可以具有类模板类型,而无需指定模板参数。CTAD将确定这些参数。

因此,您可以创建一个由size_t N模板化的类,该类具有char[N]作为成员,可以从一个构造,并且N可以通过CTAD推导出来。

例如:

// 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);
    }
};

然后你可以做 template <ConstString S> struct A {...};,并使用S.strS.view()来查看字符串。

此类还有一些额外的便捷运算符:

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;
}

你还可以使用这个类来创建模板UDL:

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);

我尝试过类似的事情,但我找不到一种在编译时表达式中访问字符串中字符的方法。例如,我用static_assert替换了ASSERT(new_str[N-1] == '\0');,因为这就是我想要的结果,但编译器不认为new_str[N-1]是一个有效的constexpr表达式。 - Bob Builder
@BobBuilder 你不需要一个静态的。标准的assert()会起作用,因为在正常情况下它是constexpr的。我已经编辑了答案,以免混淆任何人。 - HolyBlackCat
你是在指 assert.h 中的 assert() 吗?我尝试使用了它,虽然编译通过了,但并没有在编译时检查任何内容。作为一个简单的测试,我输入了 assert(new_str[0] == something_that_gives_a_false_result),但它仍然可以编译通过,没有出现错误。 - Bob Builder
@BobBuilder 是的,但我刚刚想出了一个更好的测试,请看编辑。 - HolyBlackCat
谢谢,最后的编辑实现了编译时字符串验证。感觉有点像是一个技巧,但这是我见过唯一真正解决问题的方法。 :) - Bob Builder
@BobBuilder:每个人都希望字符串字面值成为模板参数,但是字符串字面值非常古老和特殊,使它们更加规则化比听起来困难得多。 - Davis Herring

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接