C++20 introduces concepts, a smart way to put constraints on the types a template function or class can take in.
While iterator categories and properties remain the same, what changes is how you enforce them: with tags until C++17, with concepts since C++20. For example, instead of the std::forward_iterator_tag tag you would mark your iterator with the std::forward_iterator concept.
The same thing applies to all iterator properties. For example, a Forward Iterator must be std::incrementable. This new mechanism helps in getting better iterator definitions and makes errors from the compiler much more readable.
This piece of text it's taken from this article: https://www.internalpointers.com/post/writing-custom-iterators-modern-cpp
But the author didn't upgrade the content on how to make a custom iterator on C++20 with concepts, it remains the <= C++17 tags version.
Can someone make an example on how to write a custom iterator for a custom container in a C++20 version with the concept features?
By and large, the C++20 way of defining iterators does away with explicitly tagging the type, and instead relies on concepts to just check that a given type happens to respect the iterator category's requirements.
This means that you can now safely duck-type your way to victory while supporting clean overload resolution and error messages:
struct my_iterator {
// No need for tagging or anything special, just implement the required interface.
};
If you want to ensure that a given type fulfills the requirements of a certain iterator category, you static_assert
the concept on that type:
#include <iterator>
static_assert(std::forward_iterator<my_iterator>);
Enforcing that a function only accepts a certain iterator category is done by using the concept in your template arguments.
#include <iterator>
template<std::forward_iterator Ite, std::sentinel_for<Ite> Sen>
void my_algorithm(Ite begin, Sen end) {
// ...
}
std::sentinel_for<>
is now used for the end iterator instead of using Ite
twice. It allows to optionally use a separate type for the end iterator, which is sometimes convenient, especially for input iterators.
For example:
struct end_of_stream_t {};
constexpr end_of_stream_t end_of_stream{};
struct my_input_iterator {
// N.B. Not a complete implementation, just demonstrating sentinels.
some_stream_type* data_stream;
bool operator==(end_of_stream_t) const { return data_stream->empty(); }
};
template<std::input_iterator Ite, std::sentinel_for<Ite> Sen>
void my_algorithm(Ite begin, Sen end) {
while(begin != end) {
//...
}
}
void foo(some_stream_type& stream) {
my_algorithm(my_input_iterator{&stream}, end_of_stream);
}