在类似函数式的 reduce 函数中转发和 return 类型

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

我需要创建一个类似于 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函数的参数转发和return类型上,但是我能找到

我应该如何修改我的 reduce 定义以使两个代码段都有效?

你可以试试

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

当第二个参数是右值时,此 returns 为纯右值,否则为左值引用参数。您的片段似乎是 fine with it.

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

问题案例中的 lambda:

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

returns 按值计算,所以当 reduce 递归时:

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

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

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

它 return 是绑定到在展开递归时被销毁的临时对象的引用,因此 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();

Live example.