假设我正在编写一个名为“invoke_all”的函数,它将它接收到的所有 lambda(作为模板参数)应用于参数,它看起来像:
template <auto... fs, typename... Args>
constexpr auto invoke_all(Args &&...args) {
(std::invoke(fs, std::forward<Args>(args)...), ...);
}
到目前为止一切顺利。称呼它为:
invoke_all<
[](auto i, auto j) { std::println("lambda1: {} {}", i, j); },
[](auto i, auto j) { std::println("lambda2: {} {}", i, j); }
>
(42, "abc"s);
但是假设我实际上希望这些 lambda 返回值,并将所有返回值打包在 std::tuple 中,问题就出现了。这无法编译:
#include <functional>
#include <tuple>
#include <format>
#include <string>
#include <cassert>
template <auto... fs, typename... Args>
constexpr auto invoke_all(Args &&...args) {
return std::tuple{(std::invoke(fs, std::forward<Args>(args)...), ...)};
}
using namespace std::literals;
int main() {
auto results = invoke_all< //
[](auto i, auto j) { return std::format("lambda1: {} {}", i, j); },
[](auto i, auto j) { return std::format("lambda2: {} {}", i, j); }> //
(42, "abc"s);
assert((results == std::tuple{"lambda1: 42 abc"s, "lambda2: 42 abc"s}));
}
因为“invoke_all”返回的元组是大小为 1 的元组,而不是 2。
这也不能编译:
#include <functional>
#include <tuple>
#include <format>
#include <string>
#include <cassert>
template <auto... fs, typename... Args>
constexpr auto invoke_all(Args &&...args) {
return std::tuple{std::invoke(fs, std::forward<Args>(args)...), ...};
}
using namespace std::literals;
int main() {
auto results = invoke_all< //
[](auto i, auto j) { return std::format("lambda1: {} {}", i, j); },
[](auto i, auto j) { return std::format("lambda2: {} {}", i, j); }> //
(42, "abc"s);
assert((results == std::tuple{"lambda1: 42 abc"s, "lambda2: 42 abc"s}));
}
因为包扩展周围缺少 () (我认为)。
那么有没有办法解决这个问题,即从结果包扩展中创建一个元组?
你上一次的尝试几乎就成功了。您需要记住的是
...
表示扩展为逗号分隔列表,因此当您这样做时
template <auto... fs, typename... Args>
constexpr auto invoke_all(Args &&...args) {
return std::tuple{std::invoke(fs, std::forward<Args>(args)...), ...};
}
您正在尝试执行逗号表达式,但缺少执行折叠表达式所需的外部 ()。相反,你想要的是
template <auto... fs, typename... Args>
constexpr auto invoke_all(Args &&...args) {
return std::tuple{std::invoke(fs, std::forward<Args>(args)...)...};
}
没有
,
。这扩展到
template <auto... fs, typename... Args>
constexpr auto invoke_all(Args &&...args) {
return std::tuple{std::invoke(fs1, fwd_arg1, fwd_arg2, ..., fwd_argN),
std::invoke(fs2, fwd_arg1, fwd_arg2, ..., fwd_argN)
...,
std::invoke(fsN, fwd_arg1, fwd_arg2, ..., fwd_argN);
}
测试用例需要注意的另一件事是,结果不会是
std::tuple{"lambda1: 42 abc"s, "lambda2: 42 abc"s}
但是却是
std::tuple{"lambda1: 42 abc"s, "lambda2: 42"s}
这是因为
"abc"s
是一个临时变量,因此它会被移动到第一个 lambda 中,然后剩下的 lambda 剩下一个空字符串。理想情况下,您不应该转发任何内容,而只是按值传递,因此所有内容都被视为左值,例如
template <auto... fs, typename... Args>
constexpr auto invoke_all(Args &&...args) {
return std::tuple{std::invoke(fs, args...)...};
}