#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 #include // Experimenting with cpp20 concepts. // -- BASIC CONCEPTS ----------------------------------------------------------- // Type alias for an optional reference to type T. template using optref = std::optional>; // Definition of an ITERATOR concept (trait). // // The iterator implementation must provide an inner type *item* and a function // *next()*. template concept Iter = requires(T iter) { typename T::item; { iter.next() } -> std::same_as>; }; // 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 concept BoundIdxItems = requires(const T t, std::size_t idx) { typename T::value_type; { t.size() } -> std::same_as; { t[idx] } -> std::same_as; }; // -- ITER IMPLEMENTATION ------------------------------------------------------ // Iterator adaptor. // // An example implementation of the ITER concept, generically for all types that // satisfy the BOUND_IDX_ITEMS concept. template struct iter_adapter { using item = const typename Items::value_type; iter_adapter(const Items& i) : items(i) { } auto next() -> optref { return idx == items.size() ? std::nullopt : optref{items[idx++]}; } private: const Items& items; std::size_t idx = 0; }; // -- EXAMPLE: ALGORITHM USING ITER -------------------------------------------- template 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 auto max(I iter) -> optref requires CmpLt { 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 #include template void report_vector(const std::vector& vec) { printf("vec = {"); const char* sep = ""; for (const T& v : vec) { printf("%s", std::format("{}{}", sep, v).c_str()); sep = ", "; } puts("}"); } template void report_max(const std::vector& vec) { report_vector(vec); if (optref 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 vec{1, 4, -9, 42, 1, 3}; report_max(vec); } { std::vector 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 { using item = typename decltype(iter)::item; while (optref 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 logs(4); consume(iter_adapter(logs)); } } // -- EXAMPLE: ITER COMPLEX TYPE ----------------------------------------------- int main() { test_vector_max(); test_complex_type(); }