pipable_algorithms.cpp 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // Copyright (C) 2016-2018 T. Zachary Laine
  2. //
  3. // Distributed under the Boost Software License, Version 1.0. (See
  4. // accompanying file LICENSE_1_0.txt or copy at
  5. // http://www.boost.org/LICENSE_1_0.txt)
  6. //[ pipable_algorithms
  7. #include <boost/yap/algorithm.hpp>
  8. #include <algorithm>
  9. #include <vector>
  10. //[ pipable_algorithms_and_simple_range
  11. // An enum to represent all the standard algorithms we want to adapt. In this
  12. // example, we only care about std::sort() and std::unique().
  13. enum class algorithm_t { sort, unique };
  14. // Just about the simplest range template you could construct. Nothing fancy.
  15. template<typename Iter>
  16. struct simple_range
  17. {
  18. Iter begin() const { return first_; }
  19. Iter end() const { return last_; }
  20. Iter first_;
  21. Iter last_;
  22. };
  23. // This simply calls the standard algorithm that corresponds to "a". This
  24. // certainly won't work for all the algorithms, but it works for many of them
  25. // that just take a begin/end pair.
  26. template<typename Range>
  27. auto call_algorithm(algorithm_t a, Range & r)
  28. {
  29. simple_range<decltype(r.begin())> retval{r.begin(), r.end()};
  30. if (a == algorithm_t::sort) {
  31. std::sort(r.begin(), r.end());
  32. } else if (a == algorithm_t::unique) {
  33. retval.last_ = std::unique(r.begin(), r.end());
  34. }
  35. return retval;
  36. }
  37. //]
  38. // This is the transform that evaluates our piped expressions. It returns a
  39. // simple_range<>, not a Yap expression.
  40. struct algorithm_eval
  41. {
  42. // A pipe should always have a Yap expression on the left and an
  43. // algorithm_t terminal on the right.
  44. template<typename LExpr>
  45. auto operator()(
  46. boost::yap::expr_tag<boost::yap::expr_kind::bitwise_or>,
  47. LExpr && left,
  48. algorithm_t right)
  49. {
  50. // Recursively evaluate the left ...
  51. auto const left_result =
  52. boost::yap::transform(std::forward<LExpr>(left), *this);
  53. // ... and use the result to call the function on the right.
  54. return call_algorithm(right, left_result);
  55. }
  56. // A call operation is evaluated directly. Note that the range parameter
  57. // is taken as an lvalue reference, to prevent binding to a temporary and
  58. // taking dangling references to its begin and end. We let the compiler
  59. // deduce whether the lvalue reference is const.
  60. //[ tag_xform
  61. template<typename Range>
  62. auto operator()(
  63. boost::yap::expr_tag<boost::yap::expr_kind::call>,
  64. algorithm_t a,
  65. Range & r)
  66. {
  67. return call_algorithm(a, r);
  68. }
  69. //]
  70. };
  71. // This is the expression template we use for the general case of a pipable
  72. // algorithm expression. Terminals are handled separately.
  73. template<boost::yap::expr_kind Kind, typename Tuple>
  74. struct algorithm_expr
  75. {
  76. static boost::yap::expr_kind const kind = Kind;
  77. Tuple elements;
  78. // This is how we get the nice initializer semantics we see in the example
  79. // usage below. This is a bit limited though, because we always create a
  80. // temporary. It might therefore be better just to create algorithm_expr
  81. // expressions, call yap::evaluate(), and then use the sequence containers
  82. // assign() member function to do the actual assignment.
  83. template<typename Assignee>
  84. operator Assignee() const
  85. {
  86. // Exercise left for the reader: static_assert() that Assignee is some
  87. // sort of container type.
  88. auto const range = boost::yap::transform(*this, algorithm_eval{});
  89. return Assignee(range.begin(), range.end());
  90. }
  91. };
  92. // This is a bit loose, because it allows us to write "sort(v) | unique(u)" or
  93. // similar. It works fine for this example, but in production code you may
  94. // want to write out the functions generated by this macro, and add SFINAE or
  95. // concepts constraints on the right template parameter.
  96. BOOST_YAP_USER_BINARY_OPERATOR(bitwise_or, algorithm_expr, algorithm_expr)
  97. // It's useful to specially handle terminals, because we want a different set
  98. // of operations to apply to them. We don't want "sort(x)(y)" to be
  99. // well-formed, for instance, or "sort | unique" either.
  100. struct algorithm
  101. {
  102. static boost::yap::expr_kind const kind = boost::yap::expr_kind::terminal;
  103. boost::hana::tuple<algorithm_t> elements;
  104. BOOST_YAP_USER_CALL_OPERATOR_N(::algorithm_expr, 1)
  105. };
  106. // Here are ready-made Yap terminals, one for each algorithm enumerated in
  107. // algorithm_t.
  108. constexpr algorithm sort{{algorithm_t::sort}};
  109. constexpr algorithm unique{{algorithm_t::unique}};
  110. int main()
  111. {
  112. {
  113. //[ typical_sort_unique_usage
  114. std::vector<int> v1 = {0, 2, 2, 7, 1, 3, 8};
  115. std::sort(v1.begin(), v1.end());
  116. auto it = std::unique(v1.begin(), v1.end());
  117. std::vector<int> const v2(v1.begin(), it);
  118. assert(v2 == std::vector<int>({0, 1, 2, 3, 7, 8}));
  119. //]
  120. }
  121. {
  122. //[ pipable_sort_unique_usage
  123. std::vector<int> v1 = {0, 2, 2, 7, 1, 3, 8};
  124. std::vector<int> const v2 = sort(v1) | unique;
  125. assert(v2 == std::vector<int>({0, 1, 2, 3, 7, 8}));
  126. //]
  127. }
  128. }
  129. //]