【问题标题】:Forwards and return type(s) in functional-like reduce function类函数化reduce函数中的转发和返回类型
【发布时间】:2018-01-17 13:39:18
【问题描述】:

我需要创建一个类似于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;
}

但如果我这样做了,第一个 sn-p 就不再起作用了。

问题出在reduce函数的参数转发和返回类型,但是我能找到。

我应该如何修改我的 reduce 定义以使两个 sn-ps 都能工作?

【问题讨论】:

标签: c++ templates c++14 template-meta-programming temporary


【解决方案1】:

你可以试试

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

当第二个参数是右值时返回一个纯右值,否则返回一个引用该参数的左值。你的 sn-ps 好像是fine with it

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

【讨论】:

  • decltype(auto) 是一种强大的魔法,你必须非常小心你真的意思它。 +1
【解决方案2】:

问题案例中的 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

【讨论】:

    【解决方案3】:

    有人提到折叠表达式。

    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.

    【讨论】:

      猜你喜欢
      • 2021-01-31
      • 2017-08-06
      • 1970-01-01
      • 2012-06-07
      • 2015-12-16
      • 1970-01-01
      • 1970-01-01
      • 2018-04-09
      • 1970-01-01
      相关资源
      最近更新 更多