2017-08-09 27 views
5

我需要创建一个类似于std::reduce的函数reduce,但不是在容器上工作,该函数应该可用于可变参数。函数式缩减函数中的前进和返回类型

这是我目前有:

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定义以使这两个片段都能正常工作?

+0

看看C++ 17的折叠表达式http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html – Snps

回答

3

你可以尝试

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

这会返回一个prvalue当第二个参数是一个右值和左值引用的说法并非如此。您的摘录似乎是fine with it

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

+0

'decltype(auto)'是强大的魔法,你必须非常小心,你真的*意味着*它。 +1 – Yakk

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从悬挂参考初始化。

造成这种情况的最简单的解决办法是将你的拉姆达不能创建一个临时的,而是堆积在输入对象的结果,你知道至少会活到充分表达结束(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 
0

有人提到了折叠表达式。

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