C++ 从按顺序执行的其他 constexpr lambda 创建 lambda 不能是 constexpr
C++ creating lambda from other constexpr lambdas executed in order can't be constexpr
假设我想创建一个 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) 和 get
尝试 Compiler Explorer 中的代码
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);
}
你可以看到你打印了1
和2
删除 std::cout
s(不兼容 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);
}
我已经使用自己的辅助结构来充当 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。
制作funcs_tuple
static
。因为它是 constexpr
,所以这不应该真正改变您的代码如何运行的任何内容,因为对任何函数的所有调用都应该始终到达相同的值 funcs_tuple
。 (我猜,如果您出于某种原因获取它的地址,就会有所不同。)但是,它 确实 引用了 funcs_tuple
constexpr
,因为现在有一个对象由 constexpr
变量表示,而不是每次函数调用一个。 Godbolt
请注意,这不适用于 constexpr
函数。值得庆幸的是,如果封闭函数是 constexpr
,那么变量就不需要了。也就是说,你可以做 both/either
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,它按如下顺序执行其他一些 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) 和 get
尝试 Compiler Explorer 中的代码
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);
}
你可以看到你打印了1
和2
删除 std::cout
s(不兼容 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);
}
我已经使用自己的辅助结构来充当 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。
制作funcs_tuple
static
。因为它是 constexpr
,所以这不应该真正改变您的代码如何运行的任何内容,因为对任何函数的所有调用都应该始终到达相同的值 funcs_tuple
。 (我猜,如果您出于某种原因获取它的地址,就会有所不同。)但是,它 确实 引用了 funcs_tuple
constexpr
,因为现在有一个对象由 constexpr
变量表示,而不是每次函数调用一个。 Godbolt
请注意,这不适用于 constexpr
函数。值得庆幸的是,如果封闭函数是 constexpr
,那么变量就不需要了。也就是说,你可以做 both/either
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
}