类似函数的化简函数中的转发和返回类型

Forwards and return type(s) in functional-like reduce function

本文关键字:函数 转发 返回类型      更新时间:2023-10-16

我需要创建一个类似于std::reducereduce函数,但这个函数应该处理可变参数,而不是处理容器。

这是我目前拥有的:

template <typename F, typename T>
constexpr decltype(auto) reduce(F&&, T &&t) {
return std::forward<T>(t);
}
template <typename F, typename T1, typename T2, typename... Args>
constexpr decltype(auto) reduce(F&& f, T1&& t1, T2&& t2, Args&&... args) {
return reduce(
std::forward<F>(f),
std::forward<F>(f)(std::forward<T1>(t1), std::forward<T2>(t2)),
std::forward<Args>(args)...);
}

以下工作按预期工作:

std::vector<int> vec;
decltype(auto) u = reduce([](auto &a, auto b) -> auto& {
std::copy(std::begin(b), std::end(b), std::back_inserter(a));
return a;
}, vec, std::set<int>{1, 2}, std::list<int>{3, 4}, std::vector<int>{5, 6});
assert(&vec == &u); // ok
assert(vec == std::vector<int>{1, 2, 3, 4, 5, 6}); // ok

但以下方法不起作用:

auto u = reduce([](auto a, auto b) {
std::copy(std::begin(b), std::end(b), std::back_inserter(a));
return a;
}, std::vector<int>{}, std::set<int>{1, 2}, 
std::list<int>{3, 4}, std::vector<int>{5, 6});

这基本上崩溃了 - 为了完成这项工作,我需要将reduce的第一个定义更改为:

template <typename F, typename T>
constexpr auto reduce(F&&, T &&t) {
return t;
}

但是如果我这样做,第一个片段就不再起作用了。

问题在于reduce函数的参数和返回类型的转发,但我可以找到它。

我应该如何修改我的reduce定义以使两个代码段都正常工作?

你可以试试

template <typename F, typename T>
constexpr T reduce(F&&, T &&t) {
return std::forward<T>(t);
}

当第二个参数是右值时,这将返回一个 prvalue,否则返回一个引用参数的左值。你的片段似乎很好。

或者,只需使用您的第二个变体并vec包装在std::ref中,比照使用。这也是模板按值处理对象的标准方法。

问题案例中的 lambda:

[](auto a, auto b) {
std::copy(std::begin(b), std::end(b), std::back_inserter(a));
return a;
}

按值返回,因此当reduce递归时:

return reduce(
std::forward<F>(f),
std::forward<F>(f)(std::forward<T1>(t1), std::forward<T2>(t2)), // HERE
std::forward<Args>(args)...);

第二个参数是从该按值返回对象临时初始化的参数。当递归最终终止时:

template <typename F, typename T>
constexpr decltype(auto) reduce(F&&, T &&t) {
return std::forward<T>(t);
}

它返回绑定到该临时对象的引用,该引用在展开递归时被销毁,以便从悬空引用初始化v

最简单的解决方法是不要在lambda中创建临时,而是将结果累积在输入对象中,该输入对象至少会持续到完整表达式(DEMO(结束:

auto fn = [](auto&& a, auto const& b) -> decltype(auto) {
std::copy(std::begin(b), std::end(b), std::back_inserter(a));
// Or better:
// a.insert(std::end(a), std::begin(b), std::end(b));
return static_cast<decltype(a)>(a);
};
std::vector<int> vec;
decltype(auto) u = reduce(fn, vec,
std::set<int>{1, 2}, std::list<int>{3, 4}, std::vector<int>{5, 6});
assert(&vec == &u); // ok
assert((vec == std::vector<int>{1, 2, 3, 4, 5, 6})); // ok
auto v = reduce(fn, std::vector<int>{},
std::set<int>{1, 2},  std::list<int>{3, 4}, std::vector<int>{5, 6});
assert((v == std::vector<int>{1, 2, 3, 4, 5, 6})); // ok

有人提到了折叠表达式。

template<class F, class T=void>
struct reduce_t;
template<class F>
reduce_t<F> reduce( F&& f );
template<class F, class T>
reduce_t<F, T> reduce( F&& f, T&& t );
template<class F, class T>
struct reduce_t {
F f;
T t;
template<class Rhs>
auto operator|( Rhs&& rhs )&&{
return reduce( f, f( std::forward<T>(t), std::forward<Rhs>(rhs) ) );
}
T get()&&{ return std::forward<T>(t); }
};
template<class F>
struct reduce_t<F,void> {
F f;
template<class Rhs>
auto operator|( Rhs&& rhs )&&{
return reduce( f, std::forward<Rhs>(rhs) );
}
};
template<class F>
reduce_t<F> reduce( F&& f ) {
return {std::forward<F>(f)};
}
template<class F, class T>
reduce_t<F, T> reduce( F&& f, T&& t ) {
return {std::forward<F>(f), std::forward<T>(t)};
}
template<class F, class T, class...Ts>
auto reduce( F&& f, T&& t, Ts&&...ts ) {
return (reduce( std::forward<F>(f), std::forward<T>(t) ) | ... |  std::forward<Ts>(ts));
}

然后这些中的任何一个都可以工作:

decltype(auto) u = (reduce([](auto &a, auto b) -> auto& {
std::copy(std::begin(b), std::end(b), std::back_inserter(a));
return a;
}) | vec | std::set<int>{1, 2} | std::list<int>{3, 4} | std::vector<int>{5, 6}).get();
decltype(auto) u = reduce([](auto &a, auto b) -> auto& {
std::copy(std::begin(b), std::end(b), std::back_inserter(a));
return a;
}, vec, std::set<int>{1, 2}, std::list<int>{3, 4}, std::vector<int>{5, 6}).get();
auto u_val = (
reduce([](auto a, auto b) {
std::copy(std::begin(b), std::end(b), std::back_inserter(a));
return a;
})
| std::vector<int>{} | std::set<int>{1, 2}
| std::list<int>{3, 4} | std::vector<int>{5, 6}
).get();

活生生的例子。