Formats and return types in a function-like reduction function

I need to create a reduce function similar to std::reduce , but instead of working with containers, this function should work with variational parameters.

This is what I have:

 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)...); } 

The following works as expected:

 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 

But the following does not work:

 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}); 

This is mostly emergency. To do this, I need, for example, change the first reduce definition to:

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

But if I do, the first snippet no longer works.

The problem is with parameter passing and the return type of the reduce function, but I can find it.

How do I change reduce definitions so that both of these snippets work?

+5
source share
3 answers

You can try

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

Returns the value of prvalue when the second argument is rvalue, and lvalue, referring to the argument otherwise. Your fragments seem to be excellent with it .

Alternatively, just use your second option and wrap vec in std::ref , mutatis mutandis. This is also a standard approach when templates process objects by value.

+3
source

Lambda in your problematic case:

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

returns a value, therefore, when reduce recurses:

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

The second argument is temporary initialized from this return value object. When the recursion finally completes:

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

It returns a link bound to this temporary object, which is destroyed when unwinding the recursion, so that v initialized from the dangling link.

The simplest fix for this would be NOT to create a temporary in your lambda and instead accumulate the results in the original object, which, as you know, will live at least until the end of the full expression ( 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 
+2
source

Someone mentioned fold expressions.

 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)); } 

then any of these works:

 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(); 

Living example .

0
source

Source: https://habr.com/ru/post/1270718/


All Articles