2016-03-09 262 views
4

我寫了一個for_eachtuple S:我可以在這裏避免模板遞歸嗎?

template <typename Tuple, typename F, size_t begin, size_t end> 
enable_if_t<begin == end || tuple_size<Tuple>::value < end> for_each(Tuple&, F&&) { 
} 

template <typename Tuple, typename F, size_t begin = 0U, size_t end = tuple_size<Tuple>::value> 
enable_if_t<begin < end && tuple_size<Tuple>::value >= end> for_each(Tuple& t, F&& f) { 
    f(get<begin>(t)); 
    for_each<Tuple, F, begin + 1, end>(t, forward<F>(f)); 
} 

[Live Example]

Yakk's answer to this question給出瞭如何處理所有tuple值運行的拉姆達非遞歸一個很好的例子:

namespace detail { 
    template<class F, class...Args> 
    void for_each_arg(F&& f, Args&&...args) { 
     using detail = int[]; 

     static_cast<void>(detail{((f(std::forward<Args>(args))), void(), 0)..., 0}); 
    } 
} 

template <typename F, typename Tuple> 
void for_each_tuple_element(F&& f, Tuple&& t) { 
    return experimental::apply([&](auto&&...args) { detail::for_each_arg(forward<F>(f), decltype(args)(args)...); }, forward<Tuple>(t)); 
} 

這需要apply。你可以看到我Yakk的答案在這裏的簡化:http://ideone.com/yAYjmw

我的問題是這樣的:有沒有辦法以某種方式改造for_each_tuple_element射程,避免我的代碼即被遞歸?我試過構建由範圍定義的tuple的子集,但我似乎無法做到這一點,而不使用遞歸,然後爲什麼不只是我的for_each

+0

你可能應該能夠用'index_sequence'完成這項工作。如果你很幸運並且你的實現有非遞歸的內部'index_sequence',你將會避免遞歸。 – SergeyA

+0

每個迭代器'值'都需要它自己的類型,因爲std :: get <>的模板參數必須是一個constexpr。我認爲遞歸是不可避免的 –

+0

@SergeyA我實際上花了一些時間看'integer_sequence's,但我不能確定如何修改它們的開始和結束值,所以我不能有效地做一個範圍。如果在我的研究中有一些我錯過了,請賜教! –

回答

5

您可以通過生成std::get<Is>(t)...函數的調用序列來避免遞歸,其中Is索引的範圍從begin直到end-1。這是相當容易產生開始定索引處連續的數字序列,因爲它足以讓起點的偏移量,然後將其添加到普通索引序列的每個項目,如:

std::get<begin + 0>(t), std::get<begin + 1>(t), ... std::get<begin + n>(t) 

其中序列的長度等於beginend之間的距離。

#include <tuple> 
#include <type_traits> 
#include <utility> 
#include <cstddef> 
#include <limits> 

template <std::size_t begin, typename Tuple, typename F, std::size_t... Is> 
void for_each(Tuple&& t, F&& f, std::index_sequence<Is...>) 
{ 
    using expand = int[]; 
    static_cast<void>(expand{ 0, (f(std::get<begin + Is>(std::forward<Tuple>(t))), void(), 0)... }); 
} 

template <std::size_t begin = 0U, std::size_t end = std::numeric_limits<std::size_t>::max(), typename Tuple, typename F> 
void for_each(Tuple&& t, F&& f) 
{ 
    for_each<begin>(std::forward<Tuple>(t), std::forward<F>(f) 
        , std::make_index_sequence<(end==std::numeric_limits<std::size_t>::max()?std::tuple_size<std::decay_t<Tuple>>::value:end)-begin>{}); 
} 

測試:

int main() 
{ 
    auto t = std::make_tuple(3.14, "Hello World!", -1); 
    auto f = [](const auto& i) { std::cout << i << ' '; }; 

    for_each<1>(t, f); 

    for_each<1,3>(t, f); 

    for_each<0,2>(t, f); 
} 

DEMO

另外,需要注意的是拖欠函數模板的模板參數沒有被放置在一個模板參數列表的末尾,因此,你可以避開醜陋的decltype(t), decltype(f)部分。這意味着end不能默認爲std::tuple_size<Tuple>::value(因爲Tupleend後),但在這一點上,你需要的是一些默認的幻數。

+0

非常聰明,我沒有想到保留'begin'並將它用作偏移量。 –

+0

您可以使用'typename std :: decay :: type'。如果你有C++ 14,你可以使用'decay_t '。 (你必須使用'make_index_sequence'。) –

+0

我認爲這個答案值得我接受。但是,對於只從我的問題中熟悉這個主題的讀者來說,這需要更多的解釋。你能用幾句話來說明你的解決方案在做什麼嗎? –

2

你可以實現一個make_index_range元函數,像這樣:

template <std::size_t Start, std::size_t End> 
struct index_range { 
    template <std::size_t... Idx> 
    static std::index_sequence<(Idx + Start)...> 
    make_range (std::index_sequence<Idx...>); 

    using type = decltype(make_range(std::make_index_sequence<End-Start>{})); 
}; 

template <std::size_t Start, std::size_t End> 
using make_index_range = typename index_range<Start, End>::type; 

然後你可以用它來生成你std::index_sequence

template <typename Tuple, typename F, std::size_t... Idx> 
void for_each(Tuple& t, F&& f, std::index_sequence<Idx...>) { 
    (void)std::initializer_list<int> { 
     (std::forward<F>(f)(std::get<Idx>(t)), 0)... 
    }; 
} 

template <typename Tuple, size_t begin = 0U, 
      size_t end = tuple_size<Tuple>::value, typename F> 
enable_if_t<begin < end && tuple_size<Tuple>::value >= end> 
for_each(Tuple& t, F&& f) { 
    for_each(t, std::forward<F>(f), make_index_range<begin, end>{}); 
} 

你會使用這個像這樣:

auto t = std::make_tuple(1, 42.1, "hello world"); 
for_each<decltype(t), 2, 3>(t,[](auto e){std::cout << e << '\n';}); 
//outputs hello world 

請注意,如果您需要通過decltype(t) nt來開始和結束。您可以通過使用Peter Skotnicki的答案中的技巧來避免這種情況。

Live Demo

2

通過@Piotr啓發,但有額外的可愛:-)

#include <tuple> 
#include <utility> 
#include <tuple> 
#include <cstddef> 
#include <string> 
#include <iostream> 

template<class Tuple, size_t I> 
struct tuple_iterator 
{ 
    constexpr tuple_iterator(Tuple& p) : _p(p) {} 

    static constexpr auto index() { return I; } 
    constexpr auto operator++() const { return tuple_iterator<Tuple, I+1>(_p); } 
    constexpr auto operator--() const { return tuple_iterator<Tuple, I-1>(_p); } 
    constexpr decltype(auto) deref() const { return _p; } 

    Tuple& _p; 
}; 

template<class...Ts> 
constexpr auto begin(const std::tuple<Ts...>& t) { 
    return tuple_iterator<const std::tuple<Ts...>, 0>(t); 
} 

template<class...Ts> 
constexpr auto end(const std::tuple<Ts...>& t) { 
    return tuple_iterator<const std::tuple<Ts...>, sizeof...(Ts)>(t); 
} 

template<class Tuple, size_t I> 
constexpr auto prev(tuple_iterator<Tuple, I> it) { return --it; } 

template<class Tuple, size_t I> 
constexpr auto next(tuple_iterator<Tuple, I> it) { return ++it; } 

namespace detail 
{ 
    template < 
    std::size_t begin, 
    typename Tuple, 
    typename F, 
    std::size_t... Is 
    > 
    void for_each(Tuple&& t, F&& f, std::index_sequence<Is...>) 
    { 
     using expand = int[]; 
     static_cast<void>(expand{ 0, (f(std::get<begin + Is>(std::forward<Tuple>(t))), void(), 0)... }); 
    } 
} 

template<class Tuple, size_t First, size_t Last, class Func> 
void for_each(tuple_iterator<Tuple, First> first, tuple_iterator<Tuple, Last> last, Func&& f) 
{ 
    constexpr auto dist = Last - First; 
    constexpr auto base = First; 
    constexpr auto extent = std::make_index_sequence<dist>(); 
    detail::for_each<base>(first.deref(), std::forward<Func>(f), extent); 
} 

int main() 
{ 
    using namespace std; 

    auto x = make_tuple("dont print me", 1, "two", "three"s, "or me"); 

    for_each(next(begin(x)), prev(end(x)), [](const auto& x) { cout << x << endl; }); 

    return 0; 
} 

預計業績:

1 
two 
three 
+0

我的天啊,我不認爲這是可能的。你真的可以在'std :: for_each'中使用它! –

+0

嗯,我想*技術上*我們被允許在這種情況下專門化std :: for_each,因爲涉及的類型都是UDT? –

+0

UDT代表什麼? –

2

很抱歉的第二個答案,但我覺得這是值得。

呈現函數make_poly_tuple_iterator()它返回一個迭代器,該迭代器將迭代元組中的元素,並使用std名稱空間中的所有算法。

取消引用迭代器會生成boost :: variant <引用類型...>所以函子必須是boost::static_visitor<>的專業化版本。下面

演示,使用這樣的:

int main() 
{ 
    using namespace std; 

    auto x = make_tuple(tagged_string<tag1>("dont print me"), 
         1, 
         2.0, 
         tagged_string<tag2>("three"), 
         tagged_string<tag3>("or me")); 

    // this is the statically typed version 
    for_each(next(begin(x)), 
      prev(end(x)), 
      [](const auto& x) { cout << x << endl; }); 

    // and the polymorphic version 
    auto first = std::next(make_poly_tuple_iterator(begin(x))); 
    auto last = std::prev(make_poly_tuple_iterator(end(x))); 
    // note: std::for_each ;-) 
    std::for_each(first, last, print_it()); 

    return 0; 
} 

預計業績:

1 
2 
three 
printing: 1 
printing: 2 
printing: three 

完整代碼:

是的,我知道,有可以做出很多很多的改進。 ...

#include <tuple> 
#include <utility> 
#include <tuple> 
#include <cstddef> 
#include <string> 
#include <iostream> 
#include <boost/variant.hpp> 
#include <stdexcept> 
#include <exception> 


template<class Tuple, size_t I> 
struct tuple_iterator 
{ 
    constexpr tuple_iterator(Tuple& p) : _p(p) {} 

    static constexpr auto index() { return I; } 
    static constexpr auto upper_bound() { return std::tuple_size<Tuple>::value; } 
    static constexpr auto lower_bound() { return 0; } 
    template<size_t I2> static constexpr auto ValidIndex = I2 >= lower_bound() && I2 < upper_bound(); 
    template<size_t I2, typename = void> 
    struct de_ref_type { using type = decltype(std::get<0>(std::declval<Tuple>())); }; 
    template<size_t I2> 
    struct de_ref_type<I2, std::enable_if_t<ValidIndex<I2>>> 
    { using type = decltype(std::get<I2>(std::declval<Tuple>())); }; 

    template<size_t I2> using DerefType = typename de_ref_type<I2>::type; 

    constexpr auto operator++() const { return make_like_me<I+1>(); } 
    constexpr auto operator--() const { return make_like_me<I-1>(); } 

    template<size_t I2, std::enable_if_t<(I2 < lower_bound())>* = nullptr> 
    constexpr auto make_like_me() const { return tuple_iterator<Tuple, lower_bound()>(_p); } 

    template<size_t I2, std::enable_if_t<(I2 >= upper_bound())>* = nullptr> 
    constexpr auto make_like_me() const { return tuple_iterator<Tuple, upper_bound()>(_p); } 

    template<size_t I2, std::enable_if_t<ValidIndex<I2>>* = nullptr> 
    constexpr auto make_like_me() const { return tuple_iterator<Tuple, I2>(_p); } 

    constexpr decltype(auto) deref() const { return _p; } 

    template<size_t X> bool operator==(const tuple_iterator<Tuple, X>& r) const { return false; } 
    bool operator==(const tuple_iterator<Tuple, I>& r) const { 
     return std::addressof(_p) == std::addressof(r._p); 
    } 

    template<size_t I2, std::enable_if_t<ValidIndex<I2>>* =nullptr> 
    DerefType<I2> impl_star() const { return std::get<I2>(_p); } 

    template<size_t I2, std::enable_if_t<not ValidIndex<I2>>* =nullptr> 
    DerefType<I2> impl_star() const 
    { throw std::logic_error("out of range"); } 

    decltype(auto) operator*() const { 
     return impl_star<index()>(); 
    } 

    Tuple& _p; 
}; 

template<class...Ts> 
constexpr auto begin(const std::tuple<Ts...>& t) { 
    return tuple_iterator<const std::tuple<Ts...>, 0>(t); 
} 

template<class...Ts> 
constexpr auto end(const std::tuple<Ts...>& t) { 
    return tuple_iterator<const std::tuple<Ts...>, sizeof...(Ts)>(t); 
} 

template<class Tuple, size_t I> 
constexpr auto prev(tuple_iterator<Tuple, I> it) { return --it; } 

template<class Tuple, size_t I> 
constexpr auto next(tuple_iterator<Tuple, I> it) { return ++it; } 

namespace detail 
{ 
    template < 
    std::size_t begin, 
    typename Tuple, 
    typename F, 
    std::size_t... Is 
    > 
    void for_each(Tuple&& t, F&& f, std::index_sequence<Is...>) 
    { 
     using expand = int[]; 
     static_cast<void>(expand{ 0, (f(std::get<begin + Is>(std::forward<Tuple>(t))), void(), 0)... }); 
    } 
} 

template<class Tuple, size_t First, size_t Last, class Func> 
void for_each(tuple_iterator<Tuple, First> first, tuple_iterator<Tuple, Last> last, Func&& f) 
{ 
    constexpr auto dist = Last - First; 
    constexpr auto base = First; 
    constexpr auto extent = std::make_index_sequence<dist>(); 
    detail::for_each<base>(first.deref(), std::forward<Func>(f), extent); 
} 

namespace detail { 

    template<class Tuple> 
    struct variant_of_tuple; 

    template<class...Ts> 
    struct variant_of_tuple<std::tuple<Ts...>> 
    { 
     // todo: some work to remove duplicates 
     using type = boost::variant<std::add_lvalue_reference_t<Ts>...>; 
    }; 

    template<class...Ts> 
    struct variant_of_tuple<const std::tuple<Ts...>> 
    { 
     // todo: some work to remove duplicates 
     using type = boost::variant<std::add_lvalue_reference_t<std::add_const_t<Ts>>...>; 
    }; 
} 

template<class Tuple> 
using ToVariant = typename detail::variant_of_tuple<Tuple>::type; 

template<class Tuple> 
struct poly_tuple_iterator 
{ 
    using tuple_type = Tuple; 
    using value_type = ToVariant<std::remove_reference_t<tuple_type>>; 
    using difference_type = std::ptrdiff_t; 
    using pointer = value_type*; 
    using reference = value_type&; 
    using iterator_category = std::random_access_iterator_tag; 


    struct concept { 
     virtual ~concept() = default; 
     virtual const std::type_info& type() const = 0; 
     virtual const void* address() const = 0; 
     virtual bool equal(const void* p) const = 0; 
     virtual std::unique_ptr<concept> clone() const = 0; 
     virtual std::unique_ptr<concept> next() const = 0; 
     virtual std::unique_ptr<concept> prev() const = 0; 
     virtual value_type deref() const = 0; 
    }; 

    template<size_t I> 
    struct model : concept { 
     using my_type = tuple_iterator<tuple_type, I>; 
     model(my_type iter) : _iter(iter) {} 
     const std::type_info& type() const override { return typeid(_iter); } 
     const void* address() const override { return std::addressof(_iter); } 
     std::unique_ptr<concept> clone() const override { return std::make_unique<model<I>>(_iter); }; 
     std::unique_ptr<concept> next() const override { 
      auto next_iter = ++_iter; 
      return std::make_unique<model<next_iter.index()>>(next_iter); 
     }; 
     std::unique_ptr<concept> prev() const override { 
      auto next_iter = --_iter; 
      return std::make_unique<model<next_iter.index()>>(next_iter); 
     }; 
     value_type deref() const override { return { *_iter }; } 
     bool equal(const void* p) const override { 
      return _iter == *reinterpret_cast<const my_type*>(p); 
     } 
     my_type _iter; 
    }; 

    template<size_t I> 
    poly_tuple_iterator(tuple_iterator<tuple_type, I> iter) 
    : _impl(std::make_unique<model<I>>(iter)) 
    {} 

    poly_tuple_iterator(const poly_tuple_iterator& r) : _impl(r._impl->clone()) {}; 
    poly_tuple_iterator(poly_tuple_iterator&& r) : _impl(std::move(r._impl)) {}; 
    poly_tuple_iterator& operator=(const poly_tuple_iterator& r) { 
     _impl = r._impl->clone(); 
     return *this; 
    } 
    poly_tuple_iterator& operator=(poly_tuple_iterator&& r) { 
     auto tmp = r._impl->clone(); 
     std::swap(tmp, _impl); 
     return *this; 
    } 

    value_type operator*() const { return _impl->deref(); } 
    poly_tuple_iterator& operator++() { _impl = _impl->next(); return *this; } 
    poly_tuple_iterator operator++(int) { auto tmp = *this; _impl = _impl->next(); return tmp; } 
    poly_tuple_iterator& operator--() { _impl = _impl->prev(); return *this; } 
    poly_tuple_iterator operator--(int) { auto tmp = *this; _impl = _impl->prev(); return tmp; } 
    poly_tuple_iterator& operator+=(difference_type dist) { 
     while (dist > 0) { 
      ++(*this); 
      --dist; 
     } 
     while(dist < 0) { 
      --(*this); 
      ++dist; 
     } 
     return *this; 
    } 
    bool operator==(const poly_tuple_iterator& r) const { 
     return _impl->type() == r._impl->type() 
     and _impl->equal(r._impl->address()); 
    } 
    bool operator!=(const poly_tuple_iterator& r) const { 
     return not (*this == r); 
    } 

private: 
    std::unique_ptr<concept> _impl; 
}; 

template<class Tuple, size_t I> 
auto make_poly_tuple_iterator(tuple_iterator<Tuple, I> iter) { 
    return poly_tuple_iterator<Tuple>(iter); 
} 

struct print_it : boost::static_visitor<void> 
{ 

    template<class T> 
    void operator()(const T& t) const { 
     std::cout << "printing: " << t << std::endl; 
    } 

    template<class...Ts> 
    void operator()(const boost::variant<Ts...>& v) const { 
     boost::apply_visitor(*this, v); 
    } 
}; 

// to differentiate string types for this demo 
template<class tag> 
struct tagged_string : std::string 
{ 
    using std::string::string; 
}; 

struct tag1 {}; 
struct tag2 {}; 
struct tag3 {}; 

int main() 
{ 
    using namespace std; 

    auto x = make_tuple(tagged_string<tag1>("dont print me"), 
         1, 
         2.0, 
         tagged_string<tag2>("three"), 
         tagged_string<tag3>("or me")); 

    for_each(next(begin(x)), prev(end(x)), [](const auto& x) { cout << x << endl; }); 

    auto first = std::next(make_poly_tuple_iterator(begin(x))); 
    auto last = std::prev(make_poly_tuple_iterator(end(x))); 
    std::for_each(first, last, print_it()); 

    return 0; 
} 
+0

這是一個緊張的工作,所以我覺得有義務給你一個+1。我個人希望有更少的Boost參與,但那只是我。 –

+1

@JonathanMee有一天,當我們得到一個std ::變體時,它可以被消除。 –