std::ranges::range
From cppreference.com
Defined in header <ranges>


template< class T > concept range = __RangeImpl<T&>; // expositiononly definition 
(1)  
// expositiononly helper concept template< class T > 
(2)  
// expositiononly helper concept template< class T > 
(3)  
1) The
range
concept defines the requirements of a type that allows iteration over its elements by providing an iterator and sentinel that denote the elements of the range.
Formally, given an expression E such that decltype((E)) is T, T models range only if
 [ranges::begin(E), ranges::end(E)) denotes a range, and
 both ranges::begin(E) and ranges::end(E) are amortized constant time and nonmodifying, and
 if the type of ranges::begin(E) models forward_iterator, ranges::begin(E) is equalitypreserving (in other words, forward iterators support multipass algorithms)
ranges::begin(std::forward<T>(t))
and ranges::end(std::forward<T>(t))
do not require implicit expression variations.3) The expositiononly concept __ForwardingRange defines the requirements of a range such that a function can take it by value and return iterators obtained from it without danger of dangling.
Formally, given an expression E such that decltype((E)) is T and an lvalue t that denotes the same object as E, T models __ForwardingRange only if
 ranges::begin(E) and ranges::begin(t) are expressionequivalent, and
 ranges::end(E) and ranges::end(t) are expressionequivalent, and
 the validity of iterators obtained from the object denoted by E is not tied to the lifetime of that object.
[edit] Expressionequivalent
Expression e is expressionequivalent to expression f, if e and f have the same effects, either are both potentiallythrowing or are both not potentiallythrowing (i.e. noexcept(e) == noexcept(f)), and either are both constant subexpressions or are both not constant subexpressions.