summaryrefslogtreecommitdiff
path: root/Ix/CPP/src/cpplinq/linq_selectmany.hpp
blob: 85f0d42446219fbfe234eef7413cd9cc3451f5b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

#pragma once

#include "util.hpp"
#include "linq_cursor.hpp"

#include <type_traits>

namespace cpplinq
{
    namespace detail
    {
        struct default_select_many_selector
        {
            template <class T1, class T2>
            auto operator()(T1&& t1, T2&& t2) const
                -> decltype(std::forward<T2>(t2))
            {
                return std::forward<T2>(t2);
            }
        };
    }

    namespace detail 
    {
        template <typename Fn, typename Arg>
        struct resolve_select_many_fn_return_type
        {
            typedef decltype(std::declval<Fn>()(std::declval<Arg>())) value;
        };

        template <typename TCol>
        struct value_collection_adapter
        {
            value_collection_adapter(const TCol& col)
                : _collection(col){}

            value_collection_adapter(const value_collection_adapter& src)
                : _collection(src._collection) {}

            value_collection_adapter(value_collection_adapter && src)
                : _collection(std::move(src._collection)) {}

            const TCol& get() const
            {
                return _collection;
            }

            TCol& get()
            {
                return _collection;
            }

        private:
            TCol _collection;
        };

        template<typename TCol>
        struct collection_store_type 
        {
            typedef typename std::remove_reference<TCol>::type                         collection_type;
            typedef std::reference_wrapper<collection_type>                            reference_store_type;
            typedef value_collection_adapter<collection_type>                          value_store_type;

            typedef typename std::conditional<std::is_reference<TCol>::value, reference_store_type, value_store_type>::type    store;
        };
    }

    // cur<T> -> (T -> cur<element_type>) -> cur<element_type>
    template <class Container1, class Fn, class Fn2>
    class linq_select_many
    {
        template <class T> static T instance(); // for type inference

        Container1      c1;
        Fn              fn;
        Fn2             fn2;

        typedef typename Container1::cursor Cur1;
        typedef decltype(from(instance<Fn>()(instance<Cur1>().get()))) Container2;
        typedef typename Container2::cursor Cur2;

        typedef typename detail::resolve_select_many_fn_return_type<Fn, typename Cur1::element_type>::value inner_collection;

    public:
        class cursor
        {
        public:
            typedef typename util::min_cursor_category<typename Cur1::cursor_category,
                                                       typename Cur2::cursor_category,
                                                       forward_cursor_tag>::type
                cursor_category;
            typedef typename Cur2::reference_type reference_type;
            typedef typename Cur2::element_type element_type;

            typedef detail::collection_store_type<inner_collection>     collection_store_type;
            typedef typename collection_store_type::store               collection_store;
            typedef std::shared_ptr<collection_store>                   collection_store_ptr;

        private:
            // TODO: we need to lazy eval somehow, but this feels wrong.
            Cur1                                cur1;
            dynamic_cursor<reference_type>      cur2;
            Fn                                  fn;
            Fn2                                 fn2;
            collection_store_ptr                store;

        public:
            cursor(Cur1 cur1, const Fn& fn, const Fn2& fn2)
                : cur1(std::move(cur1)), fn(fn), fn2(fn2)
            {
                if (!cur1.empty())
                {
                    store = std::make_shared<collection_store>(fn(cur1.get()));
                    cur2 = from(store->get()).get_cursor();
                }
            }

            bool empty() const 
            {
                return cur2.empty();
            }

            void inc() 
            {
                cur2.inc();
                thunk();
            }

            reference_type get() const 
            {
                return fn2(cur1.get(), cur2.get());
            }

        private:
            void thunk() 
            {
                // refill cur2
                while (cur2.empty() && !cur1.empty()) {
                    cur1.inc();
                    if (cur1.empty()) 
                        break;

                    store = std::make_shared<collection_store>(fn(cur1.get()));
                    cur2 = from(store->get()).get_cursor();
                }
            }
        };

        linq_select_many(Container1 c1, Fn fn, Fn2 fn2) 
        : c1(std::move(c1)), fn(std::move(fn)), fn2(std::move(fn2))
        {
        }

        cursor get_cursor() const
        {
            return cursor(c1.get_cursor(), fn, fn2);
        }
    };
}