C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr

C++ creating lambda from other constexpr lambdas executed in order can't be constexpr

本文关键字:不能按 顺序 执行 Constexpr lambda 创建 其他 constexpr C++      更新时间:2023-10-16

假设我想创建一个lambda,它执行其他一些lambda,顺序如下:

constexpr auto some_func{[]() {
// do something
}};
constexpr auto some_other_func{[]() {
// do something else
}};
constexpr auto funcs_tuple = std::tuple(some_func, some_other_func);
constexpr auto combined_funcs = do_funcs(funcs_tuple);
combined_funcs();

我已将do_funcs功能实现为:

template <std::size_t Idx = 0, typename Tuple>
constexpr auto do_funcs(const Tuple& tup) {
return [&]() {
if constexpr (Idx < std::tuple_size_v<Tuple>) {
const auto f = std::get<Idx>(tup);
f();
do_funcs<Idx + 1>(tup)();
}
};
}

它只是按顺序执行元组中的函数。但是,生成的变量combined_funcs不能声明为constexpr,因为在对do_funcs的调用中对funcs_tuple的引用不是常量表达式。

我正在用clang(trunk(在编译器资源管理器中尝试代码,并获得

error: constexpr variable 'combined_funcs' must be initialized by a constant expression

note: reference to 'funcs_tuple' is not a constant expression

为什么这不被视为常量表达式?有没有办法让它变constexpr?

经过一番尝试和错误之后,我发现不是通过引用从do_funcs返回的lambda中捕获元组,而是通过值捕获,生成的lambda确实可以声明为constexpr,但我真的不想为每次对do_funcs的递归调用创建元组的副本。

constexpr auto do_funcs(const Tuple& tup) {
// capture by value instead of reference
//      |
//      v
return [=]() { ...

我还想制作一个助手函数,它接受一个Lambda参数包,并将其作为元组发送到do_funcs函数,如下所示:

template <typename... Funcs>
constexpr auto make_do_funcs(Funcs&&... fs) {
const auto funcs = std::tuple(std::forward<Funcs>(fs)...);
return do_funcs(funcs);
}

我使用元组而不是另一种方法的原因是:

template <typename Func, typename... Funcs>
constexpr auto do_funcs(Func&& f, Funcs&&... fs) {
return [f = std::forward<Func>(f), ... fs = std::forward<Funcs>(fs)] {
f();
if constexpr (sizeof...(fs) > 0) {
do_funcs(fs...);
}
};
}

是因为";完美捕捉";是C++20的一个特性,并且需要一个针对C++17的元组的变通方法。

为了进一步参考;解析器";对于我的组合解析器库,它在需要时执行其他一些解析器来创建更复杂的解析器。

简单的std::apply()(折叠在逗号运算符上(怎么样?

#include <iostream>
#include <tuple>
int main()
{
auto some_func_1{[]() { std::cout << "1" << std::endl; }};
auto some_func_2{[]() { std::cout << "2" << std::endl; }};
auto funcs_tuple = std::tuple{some_func_1, some_func_2};
auto do_funcs
= [](auto const & tpl)
{ std::apply([](auto ... fn){ (fn(), ...); }, tpl); };
do_funcs(funcs_tuple);
}

你可以看到你得到打印的12

删除std::couts(不兼容constexpr(并添加一些constexpr,您就有了

#include <iostream>
#include <tuple>
int main()
{
constexpr auto some_func_1{[]() { }};
constexpr auto some_func_2{[]() { }};
constexpr auto funcs_tuple = std::tuple{some_func_1, some_func_2};
constexpr auto do_funcs
= [](auto const & tpl)
{ std::apply([](auto ... fn){ (fn(), ...); }, tpl); };
do_funcs(funcs_tuple);
}

制作funcs_tuplestatic。因为它是constexpr,所以这不应该改变代码的工作方式,因为对任何函数的所有调用都应该始终达到相同的funcs_tuple值。(我想,如果您出于某种原因获取它的地址,则会有所不同。(然而,它确实引用了funcs_tupleconstexpr,因为现在有一个对象由constexpr变量表示,而不是每次调用函数都有一个。Godbolt

请注意,这不适用于constexpr函数。值得庆幸的是,如果封闭函数是constexpr,那么变量就不需要是

void func() { // func not constexpr
static constexpr auto funcs_tuple = ...;
constexpr auto combined_funcs = do_funcs(funcs_tuple);
}

// like in the question
template <typename... Funcs>
constexpr auto make_do_funcs(Funcs&&... fs) {
// funcs not constexpr or static; make_do_funcs still constexpr
const auto funcs = std::tuple(std::forward<Funcs>(fs)...);
return do_funcs(funcs)(); // or whatever
// note: you CANNOT return do_funcs(funcs), because that would be a dangling reference
// your original make_do_funcs is simply broken
}

我已经使用了自己的辅助结构来充当lambda。

template <typename... Funcs>
struct do_funcs {
constexpr do_funcs(Funcs... fs) : funcs{fs...} {}
void operator()() const {
do_rest();
}
template <std::size_t Idx = 0>
void do_rest() const {
if constexpr (Idx < sizeof...(Funcs)) {
const auto f = std::get<Idx>(funcs);
f();
do_rest<Idx + 1>();
}
}
const std::tuple<Funcs...> funcs;
};

这使得问题中给出的例子可以是constexpr。