summaryrefslogtreecommitdiffstats
path: root/src/boost/libs/yap/example/pipable_algorithms.cpp
blob: 07c71f548eeff8685be7308e07e3e45aee42583c (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
// Copyright (C) 2016-2018 T. Zachary Laine
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//[ pipable_algorithms
#include <boost/yap/algorithm.hpp>

#include <algorithm>
#include <vector>


//[ pipable_algorithms_and_simple_range
// An enum to represent all the standard algorithms we want to adapt.  In this
// example, we only care about std::sort() and std::unique().
enum class algorithm_t { sort, unique };

// Just about the simplest range template you could construct.  Nothing fancy.
template<typename Iter>
struct simple_range
{
    Iter begin() const { return first_; }
    Iter end() const { return last_; }

    Iter first_;
    Iter last_;
};

// This simply calls the standard algorithm that corresponds to "a".  This
// certainly won't work for all the algorithms, but it works for many of them
// that just take a begin/end pair.
template<typename Range>
auto call_algorithm(algorithm_t a, Range & r)
{
    simple_range<decltype(r.begin())> retval{r.begin(), r.end()};
    if (a == algorithm_t::sort) {
        std::sort(r.begin(), r.end());
    } else if (a == algorithm_t::unique) {
        retval.last_ = std::unique(r.begin(), r.end());
    }
    return retval;
}
//]

// This is the transform that evaluates our piped expressions.  It returns a
// simple_range<>, not a Yap expression.
struct algorithm_eval
{
    // A pipe should always have a Yap expression on the left and an
    // algorithm_t terminal on the right.
    template<typename LExpr>
    auto operator()(
        boost::yap::expr_tag<boost::yap::expr_kind::bitwise_or>,
        LExpr && left,
        algorithm_t right)
    {
        // Recursively evaluate the left ...
        auto const left_result =
            boost::yap::transform(std::forward<LExpr>(left), *this);
        // ... and use the result to call the function on the right.
        return call_algorithm(right, left_result);
    }

    // A call operation is evaluated directly.  Note that the range parameter
    // is taken as an lvalue reference, to prevent binding to a temporary and
    // taking dangling references to its begin and end.  We let the compiler
    // deduce whether the lvalue reference is const.
//[ tag_xform
    template<typename Range>
    auto operator()(
        boost::yap::expr_tag<boost::yap::expr_kind::call>,
        algorithm_t a,
        Range & r)
    {
        return call_algorithm(a, r);
    }
//]
};

// This is the expression template we use for the general case of a pipable
// algorithm expression.  Terminals are handled separately.
template<boost::yap::expr_kind Kind, typename Tuple>
struct algorithm_expr
{
    static boost::yap::expr_kind const kind = Kind;

    Tuple elements;

    // This is how we get the nice initializer semantics we see in the example
    // usage below.  This is a bit limited though, because we always create a
    // temporary.  It might therefore be better just to create algorithm_expr
    // expressions, call yap::evaluate(), and then use the sequence containers
    // assign() member function to do the actual assignment.
    template<typename Assignee>
    operator Assignee() const
    {
        // Exercise left for the reader: static_assert() that Assignee is some
        // sort of container type.
        auto const range = boost::yap::transform(*this, algorithm_eval{});
        return Assignee(range.begin(), range.end());
    }
};


// This is a bit loose, because it allows us to write "sort(v) | unique(u)" or
// similar.  It works fine for this example, but in production code you may
// want to write out the functions generated by this macro, and add SFINAE or
// concepts constraints on the right template parameter.
BOOST_YAP_USER_BINARY_OPERATOR(bitwise_or, algorithm_expr, algorithm_expr)

// It's useful to specially handle terminals, because we want a different set
// of operations to apply to them.  We don't want "sort(x)(y)" to be
// well-formed, for instance, or "sort | unique" either.
struct algorithm
{
    static boost::yap::expr_kind const kind = boost::yap::expr_kind::terminal;

    boost::hana::tuple<algorithm_t> elements;

    BOOST_YAP_USER_CALL_OPERATOR_N(::algorithm_expr, 1)
};

// Here are ready-made Yap terminals, one for each algorithm enumerated in
// algorithm_t.
constexpr algorithm sort{{algorithm_t::sort}};
constexpr algorithm unique{{algorithm_t::unique}};

int main()
{
    {
//[ typical_sort_unique_usage
        std::vector<int> v1 = {0, 2, 2, 7, 1, 3, 8};
        std::sort(v1.begin(), v1.end());
        auto it = std::unique(v1.begin(), v1.end());
        std::vector<int> const v2(v1.begin(), it);
        assert(v2 == std::vector<int>({0, 1, 2, 3, 7, 8}));
//]
    }
    {
//[ pipable_sort_unique_usage
        std::vector<int> v1 = {0, 2, 2, 7, 1, 3, 8};
        std::vector<int> const v2 = sort(v1) | unique;
        assert(v2 == std::vector<int>({0, 1, 2, 3, 7, 8}));
//]
    }
}
//]