summaryrefslogtreecommitdiff
path: root/concepts_iter.cc
blob: a6f429580f51f6ce2154d9c4a9b223a7c33c63e8 (plain) (blame)
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#if 0
set -xe
clang++ -std=c++20 concepts_iter.cc && ./a.out
g++     -std=c++20 concepts_iter.cc && ./a.out
rm -f a.out
exit 0
#endif

#include <concepts>
#include <optional>

// Experimenting with cpp20 concepts.

// -- BASIC CONCEPTS -----------------------------------------------------------

// Type alias for an optional reference to type T.
template <typename T>
using optref = std::optional<std::reference_wrapper<T>>;

// Definition of an ITERATOR concept (trait).
//
// The iterator implementation must provide an inner type *item* and a function
// *next()*.
template <typename T>
concept Iter = requires(T iter) {
  typename T::item;

  { iter.next() } -> std::same_as<optref<typename T::item>>;
};

// Definition of an BOUND_IDX_ITEMS concept (trait).
//
// This concept defines an indexable type, which requires different inner type
// aliases as well as functions to index into the items and get the bounds.
template <typename T>
concept BoundIdxItems = requires(const T t, std::size_t idx) {
  typename T::value_type;

  { t.size() } -> std::same_as<std::size_t>;
  { t[idx] } -> std::same_as<typename T::const_reference>;
};

// -- ITER IMPLEMENTATION ------------------------------------------------------

// Iterator adaptor.
//
// An example implementation of the ITER concept, generically for all types that
// satisfy the BOUND_IDX_ITEMS concept.
template <BoundIdxItems Items>
struct iter_adapter {
  using item = const typename Items::value_type;

  iter_adapter(const Items& i) : items(i) {
  }

  auto next() -> optref<item> {
    return idx == items.size() ? std::nullopt : optref<item>{items[idx++]};
  }

private:
  const Items& items;
  std::size_t idx = 0;
};

// -- EXAMPLE: ALGORITHM USING ITER --------------------------------------------

template <typename T>
concept CmpLt = requires(T t) { t < t; };

// Example algorithm iterating over the items and finding the max value or
// returning an empty option if there is none.
//
// Puts an additional CMP_LT bound on the items the iterator yields, as we
// need to compare them.
template <Iter I>
auto max(I iter) -> optref<typename I::item>
  requires CmpLt<typename I::item>
{
  auto max = iter.next();

  while (auto val = iter.next()) {
    if (*max < *val) {
      max = val;
    }
  }
  return max;
}

// -- EXAMPLE: VECTOR ITER FIND MAX --------------------------------------------

// Example using std::vector and our iter_adapter to invoke max().

#include <format>
#include <vector>

template <typename T>
void report_vector(const std::vector<T>& vec) {
  printf("vec = {");
  const char* sep = "";
  for (const T& v : vec) {
    printf("%s", std::format("{}{}", sep, v).c_str());
    sep = ", ";
  }
  puts("}");
}

template <typename T>
void report_max(const std::vector<T>& vec) {
  report_vector(vec);

  if (optref<const T> res = max(iter_adapter(vec))) {
    puts(std::format("Max value found: {}", res->get()).c_str());
  } else {
    puts("No max value found!");
  }
}

void test_vector_max() {
  {
    std::vector<int> vec{1, 4, -9, 42, 1, 3};
    report_max(vec);
  }
  {
    std::vector<int> vec;
    report_max(vec);
  }
}

// -- EXAMPLE: ITER COMPLEX TYPE -----------------------------------------------

#define LOG puts(__PRETTY_FUNCTION__)
struct log {
  log() {
    LOG;
  }
  ~log() {
    LOG;
  }
  log(const log&) {
    LOG;
  }
  log(log&&) {
    LOG;
  }
  log& operator=(const log&) {
    LOG;
    return *this;
  }
  log& operator=(log&&) {
    LOG;
    return *this;
  }
};
#undef LOG

// Example function template in short form using auto and requiring that the
// iter parameter satisfies the ITER concept.
// Additionally, this demonstrates the syntax to put extra constraints in the
// iter param.
//
// [1]
// https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template
void consume(Iter auto iter)
    // None sense bound, just to demonstrate the syntax.
  requires std::same_as<typename decltype(iter)::item, const log>
{
  using item = typename decltype(iter)::item;
  while (optref<item> v = iter.next()) {
    puts("consumed");
    // item& i = *v; // would just take a ref to item
    //  item i = *v; // would copy item
  }
}

void test_complex_type() {
  {
    std::vector<log> logs(4);
    consume(iter_adapter(logs));
  }
}

// -- EXAMPLE: ITER COMPLEX TYPE -----------------------------------------------

int main() {
  test_vector_max();
  test_complex_type();
}