Let s
and i
be values of type S
and I
, respectively, such that [i, s)
denotes a range.
sentinel_for<S, I>
is modeled only if:
i == s
is well-defined.- If
bool(i != s)
theni
is dereferenceable and[++i, s)
denotes a range. std::assignable_from<I&, S>
is either modeled or not satisfied.
template <class S, class I>
concept sentinel_for =
std::semiregular<S> &&
std::input_or_output_iterator<I> &&
__WeaklyEqualityComparableWith<S, I>;
class inner_sentinel
{
public:
[[nodiscard]] bool operator==(inner_sentinel const&) const
{
return true;
}
[[nodiscard]] bool operator==(inner_iterator const& rhs) const
{
return rhs._current_inner == std::ranges::end(*rhs._base);
}
/* As of C++20, the compiler uses this^ to generate these:
* sentinel != iterator
* iterator == sentinel
* iterator != sentinel
*/
};
Why use a sentinel instead
of an end
iterator?
class inner_iterator
{
/* ... */
inner_iterator(TBase const& base, base_iterator current_outer)
: _current_outer{ std::move(current_outer) }
, _current_inner{ std::ranges::begin(base) }
, _base{ std::addressof(base) }
{
correct_inner_if_needed();
}
/* ... */
};
class inner_iterator
{
/* ... */
inner_iterator(TBase const& base, base_iterator current_outer,
base_iterator current_inner)
: _current_outer{ std::move(current_outer) }
, _current_inner{ std::move(current_inner) }
, _base{ std::addressof(base) }
{
correct_inner_if_needed(); // Should I even do this..?
}
/* ... */
};
This constructor is only for end()
to use,
but nothing actually tells us that.
Sentinels separate concerns.
The definition of "end" is in its own class.