yap/example/pipable_algorithms.cpp
2018-03-03 23:37:48 -06:00

148 lines
4.9 KiB
C++

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