std::ranges::range(3)

NAME
std::ranges::range - std::ranges::range

Synopsis
   Defined in header <ranges>
   template< class T >
   concept range = requires( T& t ) {
   ranges::begin(t); // equality-preserving for forward iterators (since C++20)
   ranges::end (t);
   };
   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.
  Semantic requirements
   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 do not
       alter the value of E in a manner observable to equality-preserving expressions,
       and
     * if the type of ranges::begin(E) models forward_iterator, ranges::begin(E) is
       equality-preserving (in other words, forward iterators support multi-pass
       algorithms)
   Note: In the definition above, the required expressions ranges::begin(t) and
   ranges::end(t) do not require implicit expression variations.

Example
// Run this code
 #include <ranges>
 #include <vector>
 #include <iostream>
 template <typename T>
 struct range_t : private T {
     using T::begin, T::end; /*...*/
 };
 static_assert(std::ranges::range< range_t<std::vector<int>> >);
 template <typename T> struct scalar_t { T t{}; /* no begin/end */ };
 static_assert(not std::ranges::range< scalar_t<int> >);
 int main() {
     if constexpr (range_t<std::vector<int>> r; std::ranges::range<decltype(r)>) {
         std::cout << "r is a range\n";
     }
     if constexpr (scalar_t<int> s; not std::ranges::range<decltype(s)>) {
         std::cout << "s is not a range\n";
     }
 }

Output:
 r is a range
 s is not a range