2017-08-09 25 views
5

Potrzebuję utworzyć funkcję reduce podobną do std::reduce, ale zamiast pracować na kontenerach, ta funkcja powinna działać na parametrach wariadycznych.Typy forwardów i zwrotów w funkcjonalnej funkcji zmniejszania

To co obecnie mam:

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

Poniższe działa zgodnie z oczekiwaniami:

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 

ale następujące nie działa:

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

Zasadniczo awarii - TO spraw, żeby to działało, potrzebuję np zmienić pierwsza definicja reduce do:

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

Ale jeśli to zrobię tak, pierwszy fragment już nie działa.

Problem jest związany z przekazywaniem parametrów i typem zwrotu funkcji reduce, ale mogę ją znaleźć.

Jak mogę zmodyfikować moje definicje reduce, aby oba sekrety działały?

+0

Spójrz na składanie C++ 17 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html – Snps

Odpowiedz

3

można spróbować

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

Zwraca prvalue gdy drugi argument był rvalue i lwartością odnosząc się do argumentu inaczej. Twoje fragmenty wydają się być fine with it.

Alternatywnie, po prostu użyj swojego drugiego wariantu i zawiń vec w std::ref, mutatis mutandis. Jest to również standardowe podejście, gdy szablony obsługują obiekty według wartości.

+0

'decltype (auto)' to silna magia , trzeba być bardzo ostrożnym, żebyś naprawdę to * znaczył. +1 – Yakk

2

Lambda w Twoim przypadku problem:

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

powraca pod względem wartości, więc kiedy 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)...); 

Drugi argument jest tymczasowy zainicjowana z tego obiektu przez wartość powrotną. Kiedy rekurencja ostatecznie kończy:

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

Zwraca odwołanie związany z tym tymczasowego obiektu, który jest zniszczony podczas odwijania rekursji, tak że v jest inicjowany z dangling odniesienia.

Najprostszym rozwiązaniem tego problemu byłoby, aby nie stworzyć tymczasowy w lambda i zamiast gromadzić wyniki w obiekcie wejściowym, które znasz będzie żył co najmniej do końca pełnej ekspresji (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

Ktoś wspomniał o wyrażeniach krotności.

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

wtedy każda z tych prac:

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.