如何参数化 consteval lambda?

Jam*_*art 3 c++ c++20

背景

我使用 NTTP(非类型模板参数)lambdastring_view在编译时将 a 存储到类型中:

template<auto getStrLambda>
struct MyType {
    static constexpr std::string_view myString{getStrLambda()};
};

int main() {
    using TypeWithString = MyType<[]{return "Hello world";}>;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这有效,并实现了我的主要目的。

问题

我现在的问题是,为了使其更易于使用,我如何编写一个包装函数来为我创建 lambda

我在想这样的事情:

// This helper function generates the lambda, rather than writing the lambda inline
consteval auto str(auto&& s) {
    return [s]() consteval {
        return s;
    };
};

template<auto getStrLambda>
struct MyType {
    static constexpr std::string_view myString{getStrLambda()};
};

int main() {
    using TypeWithString = MyType<str("Hello world")>;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码在 clang 上失败了,因为 lambda 不是结构化的,因为它需要捕获字符串:

error: type '(lambda at <source>:4:12)' of non-type template parameter is not a structural type
    using TypeWithString = MyType<str("Hello world")>;
                                  ^
note: '(lambda at <source>:4:12)' is not a structural type because it has a non-static data member that is not public
    return [s]() consteval {
            ^
Run Code Online (Sandbox Code Playgroud)

如果变量被初始化为常量表达式( source ),则 lambda 可以使用变量而不捕获它,那么如何定义一个函数来在编译时参数化此 lambda 返回值?

Art*_*yer 5

这是CWG2542

在解决相关问题之前,GCC 和 Clang 在接受或拒绝该程序方面实际上并没有错误。lambda 的类型只有一个类型为 的数据成员char[12],该成员可以是公共的或私有的。看来 Clang 将他们视为私人成员。

但现在,在 CWG2542 之后,捕获 lambda 不能具有结构类型,因此您必须做其他事情。

显而易见的解决方案是显式写出闭包类型,确保数据成员始终是公共的:

consteval auto str(auto&& s) {
    static_assert(std::is_array_v<std::remove_reference_t<decltype(s)>>);
    return [&]<std::size_t... I>(std::index_sequence<I...>) {
        struct {
            std::remove_cvref_t<decltype(s)> value;
            consteval std::decay_t<decltype(s)> operator()() const {
                return value;
            }
        } functor{{std::forward<decltype(s)>(s)[I]...}};
        return functor;
    }(std::make_index_sequence<std::extent_v<std::remove_reference_t<decltype(s)>>>{});
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,对于相同长度的字符串,它将具有相同的类型,因此MyType<str("xyz")>在一个翻译单元中将与另一个翻译单元中的名称相同MyType<str("xyz")>,因为它存储一个数组。

str("string literal")作为函数调用并返回“没有任何捕获”的东西的目标是不可能的,因为函数参数auto&& s在常量表达式中不可用。特别是,您无法将其转换为指针,也无法访问其任何项目。

您还可以使用str类型并跳过函数的步骤:

template<typename T>
struct str;

template<typename T, std::size_t N>
struct str<T[N]> {
    T value[N];

    constexpr str(const str&) = default;
    consteval str(const T(& v)[N]) : str(std::make_index_sequence<N>{}, v) {}

    consteval auto operator()() const {
        return value;
    }
private:
    template<std::size_t... I>
    consteval str(std::index_sequence<I...>, const T(& v)[N]) : value{ v[I]... } {}
};

template<typename T, std::size_t N>
str(const T(&)[N]) -> str<T[N]>;
Run Code Online (Sandbox Code Playgroud)

str("string literal")现在保存数组的结构类型在哪里。

  • @JamesMart 你的 `str(auto&amp;&amp; s)` 函数中捕获 `s` 的 lambda 确实如此。`[]{return "Hello world";}` lambda 没有,但正如我所解释的,一旦您将 `s` 作为函数参数传递,它就不再是常量表达式,因此必须被捕获。 (2认同)