[libcxx-commits] [libcxx] WIP [libc++][ranges] P3052R2: `view_interface::at()` (PR #205012)

Hristo Hristov via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jun 21 22:41:04 PDT 2026


https://github.com/H-G-Hristov updated https://github.com/llvm/llvm-project/pull/205012

>From f00334e70d36c9fae3640d0bec848f81732348ad Mon Sep 17 00:00:00 2001
From: Hristo Hristov <hghristov.rmm at gmail.com>
Date: Sun, 21 Jun 2026 23:52:40 +0300
Subject: [PATCH] [libc++][ranges] P3052R2: `view_interface::at()`

Closes #204395

Implements https://wg21.link/P3052R2
---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +
 libcxx/docs/Status/Cxx29Papers.csv            |   2 +-
 libcxx/include/__ranges/view_interface.h      |  22 +++
 libcxx/include/version                        |   2 +
 .../view.interface/nodiscard.verify.cpp       |   5 +
 libcxx/test/libcxx/transitive_includes.gen.py |   2 +-
 .../test/libcxx/transitive_includes/cxx23.csv |   1 +
 .../test/libcxx/transitive_includes/cxx26.csv |   1 +
 .../ranges.version.compile.pass.cpp           |  33 ++++
 .../version.version.compile.pass.cpp          |  33 ++++
 .../view.interface/view.interface.pass.cpp    | 145 ++++++++++++------
 .../generate_feature_test_macro_components.py |   7 +
 12 files changed, 205 insertions(+), 50 deletions(-)

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 8e26bdcd860c1..e27b9433baeaa 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -308,6 +308,8 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_variant``                                      ``202106L``
     ---------------------------------------------------------- -----------------
+    ``__cpp_lib_view_interface``                               ``202606L``
+    ---------------------------------------------------------- -----------------
     **C++23**
     ----------------------------------------------------------------------------
     ``__cpp_lib_adaptor_iterator_pair_constructor``            ``202106L``
diff --git a/libcxx/docs/Status/Cxx29Papers.csv b/libcxx/docs/Status/Cxx29Papers.csv
index a5d896f260657..733eab1a3b29e 100644
--- a/libcxx/docs/Status/Cxx29Papers.csv
+++ b/libcxx/docs/Status/Cxx29Papers.csv
@@ -4,7 +4,7 @@
 "`P2414R12 <https://wg21.link/P2414R12>`__","Pointer lifetime-end zap proposed solutions","2026-06 (Brno)","","","`#204392 <https://github.com/llvm/llvm-project/issues/204392>`__","Voted as a Defect Report."
 "`P3319R6 <https://wg21.link/P3319R6>`__","Add an ``iota`` object for ``simd`` (and more)","2026-06 (Brno)","","","`#204393 <https://github.com/llvm/llvm-project/issues/204393>`__",""
 "`P3798R1 <https://wg21.link/P3798R1>`__","The unexpected in ``std::expected``","2026-06 (Brno)","","","`#204394 <https://github.com/llvm/llvm-project/issues/204394>`__",""
-"`P3052R2 <https://wg21.link/P3052R2>`__","``view_interface::at()``","2026-06 (Brno)","","","`#204395 <https://github.com/llvm/llvm-project/issues/204395>`__",""
+"`P3052R2 <https://wg21.link/P3052R2>`__","``view_interface::at()``","2026-06 (Brno)","|Complete|,"23","`#204395 <https://github.com/llvm/llvm-project/issues/204395>`__",""
 "`P4206R0 <https://wg21.link/P4206R0>`__","Revert string support in ``std::constant_wrapper``","2026-06 (Brno)","","","`#203336 <https://github.com/llvm/llvm-project/issues/203336>`__","To be applied as a Defect Report."
 "`P3395R6 <https://wg21.link/P3395R6>`__","Fix encoding issues and add a formatter for ``std::error_code``","2026-06 (Brno)","","","`#204396 <https://github.com/llvm/llvm-project/issues/204396>`__",""
 "`P3505R4 <https://wg21.link/P3505R4>`__","Fix the default floating-point representation in ``std::format``","2026-06 (Brno)","","","`#204397 <https://github.com/llvm/llvm-project/issues/204397>`__","To be applied as a Defect Report."
diff --git a/libcxx/include/__ranges/view_interface.h b/libcxx/include/__ranges/view_interface.h
index 37b2c9e2c1a75..efe263e895d16 100644
--- a/libcxx/include/__ranges/view_interface.h
+++ b/libcxx/include/__ranges/view_interface.h
@@ -15,6 +15,7 @@
 #include <__concepts/same_as.h>
 #include <__config>
 #include <__iterator/concepts.h>
+#include <__iterator/distance.h>
 #include <__iterator/iterator_traits.h>
 #include <__iterator/prev.h>
 #include <__memory/pointer_traits.h>
@@ -25,6 +26,7 @@
 #include <__type_traits/is_class.h>
 #include <__type_traits/make_unsigned.h>
 #include <__type_traits/remove_cv.h>
+#include <stdexcept>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -159,6 +161,26 @@ class view_interface {
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator[](range_difference_t<_RARange> __index) const {
     return ranges::begin(__derived())[__index];
   }
+
+  template <random_access_range _RARange = _Derived>
+    requires sized_range<_RARange> // freestanding-deleted
+  [[nodiscard]] constexpr decltype(auto) at(range_difference_t<_RARange> __index) {
+    if (__index < 0 || __index >= ranges::distance(__derived())) {
+      std::__throw_out_of_range(
+          "Precondition `0 <= __index < distance()` not satisfied. `.at(__index)` called with out-of-bounds index.");
+    }
+    return (*this)[__index];
+  }
+
+  template <random_access_range _RARange = const _Derived>
+    requires sized_range<_RARange> // freestanding-deleted
+  [[nodiscard]] constexpr decltype(auto) at(range_difference_t<_RARange> __index) const {
+    if (__index < 0 || __index >= ranges::distance(__derived())) {
+      std::__throw_out_of_range(
+          "Precondition `0 <= __index < distance()` not satisfied. `.at(__index)` called with out-of-bounds index.");
+    }
+    return (*this)[__index];
+  }
 };
 
 } // namespace ranges
diff --git a/libcxx/include/version b/libcxx/include/version
index 7f2dc9e4b72ab..744eafd35b72a 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -289,6 +289,7 @@ __cpp_lib_unwrap_ref                                    201811L <functional>
 __cpp_lib_variant                                       202306L <variant>
                                                         202106L // C++20
                                                         202102L // C++17
+__cpp_lib_view_interface                                202606L <ranges>
 __cpp_lib_void_t                                        201411L <type_traits>
 
 */
@@ -487,6 +488,7 @@ __cpp_lib_void_t                                        201411L <type_traits>
 # define __cpp_lib_unwrap_ref                           201811L
 # undef  __cpp_lib_variant
 # define __cpp_lib_variant                              202106L
+# define __cpp_lib_view_interface                       202606L
 #endif
 
 #if _LIBCPP_STD_VER >= 23
diff --git a/libcxx/test/libcxx/ranges/range.utility/view.interface/nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.utility/view.interface/nodiscard.verify.cpp
index a22c03f5d1a71..5d076211bd5b3 100644
--- a/libcxx/test/libcxx/ranges/range.utility/view.interface/nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/ranges/range.utility/view.interface/nodiscard.verify.cpp
@@ -67,4 +67,9 @@ void test() {
   v[Diff{0}];
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::as_const(v)[Diff{0}];
+
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  v.at(Diff{0});
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::as_const(v).at(Diff{0});
 }
diff --git a/libcxx/test/libcxx/transitive_includes.gen.py b/libcxx/test/libcxx/transitive_includes.gen.py
index 198a155ac0ee5..b697758575b80 100644
--- a/libcxx/test/libcxx/transitive_includes.gen.py
+++ b/libcxx/test/libcxx/transitive_includes.gen.py
@@ -54,7 +54,7 @@
 # To re-generate the list of expected headers, temporarily set this to True, and run this test.
 # Note that this needs to be done for all supported language versions of libc++:
 # for std in c++03 c++11 c++14 c++17 c++20 c++23 c++26; do <build>/bin/llvm-lit --param std=$std libcxx/test/libcxx/transitive_includes.gen.py; done
-regenerate_expected_results = False
+regenerate_expected_results = True
 
 if regenerate_expected_results:
     print(
diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index 073f698786117..f5a2c2451fcf9 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -11,6 +11,7 @@ algorithm iosfwd
 algorithm limits
 algorithm optional
 algorithm ratio
+algorithm stdexcept
 algorithm tuple
 algorithm version
 any cstdint
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index 1450a13e125ef..f5c27ff707840 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -11,6 +11,7 @@ algorithm iosfwd
 algorithm limits
 algorithm optional
 algorithm ratio
+algorithm stdexcept
 algorithm tuple
 algorithm version
 any cstdint
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
index 670b0e664721a..f42c7ede894b5 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
@@ -80,6 +80,10 @@
 #    error "__cpp_lib_ranges_zip should not be defined before c++23"
 #  endif
 
+#  ifdef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should not be defined before c++20"
+#  endif
+
 #elif TEST_STD_VER == 14
 
 #  ifdef __cpp_lib_default_template_type_for_algorithm_values
@@ -142,6 +146,10 @@
 #    error "__cpp_lib_ranges_zip should not be defined before c++23"
 #  endif
 
+#  ifdef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should not be defined before c++20"
+#  endif
+
 #elif TEST_STD_VER == 17
 
 #  ifdef __cpp_lib_default_template_type_for_algorithm_values
@@ -204,6 +212,10 @@
 #    error "__cpp_lib_ranges_zip should not be defined before c++23"
 #  endif
 
+#  ifdef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should not be defined before c++20"
+#  endif
+
 #elif TEST_STD_VER == 20
 
 #  ifdef __cpp_lib_default_template_type_for_algorithm_values
@@ -269,6 +281,13 @@
 #    error "__cpp_lib_ranges_zip should not be defined before c++23"
 #  endif
 
+#  ifndef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should be defined in c++20"
+#  endif
+#  if __cpp_lib_view_interface != 202606L
+#    error "__cpp_lib_view_interface should have the value 202606L in c++20"
+#  endif
+
 #elif TEST_STD_VER == 23
 
 #  ifdef __cpp_lib_default_template_type_for_algorithm_values
@@ -385,6 +404,13 @@
 #    error "__cpp_lib_ranges_zip should have the value 202110L in c++23"
 #  endif
 
+#  ifndef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should be defined in c++23"
+#  endif
+#  if __cpp_lib_view_interface != 202606L
+#    error "__cpp_lib_view_interface should have the value 202606L in c++23"
+#  endif
+
 #elif TEST_STD_VER > 23
 
 #  if !defined(_LIBCPP_VERSION)
@@ -516,6 +542,13 @@
 #    error "__cpp_lib_ranges_zip should have the value 202110L in c++26"
 #  endif
 
+#  ifndef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should be defined in c++26"
+#  endif
+#  if __cpp_lib_view_interface != 202606L
+#    error "__cpp_lib_view_interface should have the value 202606L in c++26"
+#  endif
+
 #endif // TEST_STD_VER > 23
 
 // clang-format on
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index d9c78b73f7e23..5328d43d32cc8 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -944,6 +944,10 @@
 #    error "__cpp_lib_variant should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should not be defined before c++20"
+#  endif
+
 #  ifdef __cpp_lib_void_t
 #    error "__cpp_lib_void_t should not be defined before c++17"
 #  endif
@@ -1946,6 +1950,10 @@
 #    error "__cpp_lib_variant should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should not be defined before c++20"
+#  endif
+
 #  ifdef __cpp_lib_void_t
 #    error "__cpp_lib_void_t should not be defined before c++17"
 #  endif
@@ -3137,6 +3145,10 @@
 #    error "__cpp_lib_variant should have the value 202102L in c++17"
 #  endif
 
+#  ifdef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should not be defined before c++20"
+#  endif
+
 #  ifndef __cpp_lib_void_t
 #    error "__cpp_lib_void_t should be defined in c++17"
 #  endif
@@ -4595,6 +4607,13 @@
 #    error "__cpp_lib_variant should have the value 202106L in c++20"
 #  endif
 
+#  ifndef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should be defined in c++20"
+#  endif
+#  if __cpp_lib_view_interface != 202606L
+#    error "__cpp_lib_view_interface should have the value 202606L in c++20"
+#  endif
+
 #  ifndef __cpp_lib_void_t
 #    error "__cpp_lib_void_t should be defined in c++20"
 #  endif
@@ -6281,6 +6300,13 @@
 #    error "__cpp_lib_variant should have the value 202106L in c++23"
 #  endif
 
+#  ifndef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should be defined in c++23"
+#  endif
+#  if __cpp_lib_view_interface != 202606L
+#    error "__cpp_lib_view_interface should have the value 202606L in c++23"
+#  endif
+
 #  ifndef __cpp_lib_void_t
 #    error "__cpp_lib_void_t should be defined in c++23"
 #  endif
@@ -8303,6 +8329,13 @@
 #    error "__cpp_lib_variant should have the value 202306L in c++26"
 #  endif
 
+#  ifndef __cpp_lib_view_interface
+#    error "__cpp_lib_view_interface should be defined in c++26"
+#  endif
+#  if __cpp_lib_view_interface != 202606L
+#    error "__cpp_lib_view_interface should have the value 202606L in c++26"
+#  endif
+
 #  ifndef __cpp_lib_void_t
 #    error "__cpp_lib_void_t should be defined in c++26"
 #  endif
diff --git a/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp b/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp
index bce13c38f2dab..23512ccc15517 100644
--- a/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp
+++ b/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp
@@ -21,17 +21,17 @@
 #include "test_macros.h"
 #include "test_iterators.h"
 
-template<class T>
+template <class T>
 concept ValidViewInterfaceType = requires { typename std::ranges::view_interface<T>; };
 
-struct Empty { };
+struct Empty {};
 
 static_assert(!ValidViewInterfaceType<void>);
 static_assert(!ValidViewInterfaceType<void*>);
 static_assert(!ValidViewInterfaceType<Empty*>);
 static_assert(!ValidViewInterfaceType<Empty const>);
-static_assert(!ValidViewInterfaceType<Empty &>);
-static_assert( ValidViewInterfaceType<Empty>);
+static_assert(!ValidViewInterfaceType<Empty&>);
+static_assert(ValidViewInterfaceType<Empty>);
 
 using InputIter = cpp20_input_iterator<const int*>;
 
@@ -50,8 +50,8 @@ struct SizedInputRange : std::ranges::view_interface<SizedInputRange> {
 static_assert(std::ranges::sized_range<SizedInputRange>);
 
 struct NotSizedSentinel {
-  using value_type = int;
-  using difference_type = std::ptrdiff_t;
+  using value_type       = int;
+  using difference_type  = std::ptrdiff_t;
   using iterator_concept = std::forward_iterator_tag;
 
   explicit NotSizedSentinel() = default;
@@ -66,9 +66,7 @@ static_assert(std::forward_iterator<NotSizedSentinel>);
 using ForwardIter = forward_iterator<int*>;
 
 // So that we conform to sized_sentinel_for.
-constexpr std::ptrdiff_t operator-(const ForwardIter& x, const ForwardIter& y) {
-    return base(x) - base(y);
-}
+constexpr std::ptrdiff_t operator-(const ForwardIter& x, const ForwardIter& y) { return base(x) - base(y); }
 
 struct ForwardRange : std::ranges::view_interface<ForwardRange> {
   int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
@@ -78,19 +76,17 @@ struct ForwardRange : std::ranges::view_interface<ForwardRange> {
 static_assert(std::ranges::view<ForwardRange>);
 
 struct MoveOnlyForwardRange : std::ranges::view_interface<MoveOnlyForwardRange> {
-  int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
-  MoveOnlyForwardRange(MoveOnlyForwardRange const&) = delete;
-  MoveOnlyForwardRange(MoveOnlyForwardRange &&) = default;
-  MoveOnlyForwardRange& operator=(MoveOnlyForwardRange &&) = default;
-  MoveOnlyForwardRange() = default;
+  int buff[8]                                             = {0, 1, 2, 3, 4, 5, 6, 7};
+  MoveOnlyForwardRange(MoveOnlyForwardRange const&)       = delete;
+  MoveOnlyForwardRange(MoveOnlyForwardRange&&)            = default;
+  MoveOnlyForwardRange& operator=(MoveOnlyForwardRange&&) = default;
+  MoveOnlyForwardRange()                                  = default;
   constexpr ForwardIter begin() const { return ForwardIter(const_cast<int*>(buff)); }
   constexpr ForwardIter end() const { return ForwardIter(const_cast<int*>(buff) + 8); }
 };
 static_assert(std::ranges::view<MoveOnlyForwardRange>);
 
-struct MI : std::ranges::view_interface<InputRange>,
-            std::ranges::view_interface<MoveOnlyForwardRange> {
-};
+struct MI : std::ranges::view_interface<InputRange>, std::ranges::view_interface<MoveOnlyForwardRange> {};
 static_assert(!std::ranges::view<MI>);
 
 struct EmptyIsTrue : std::ranges::view_interface<EmptyIsTrue> {
@@ -131,7 +127,7 @@ struct DataIsNull : std::ranges::view_interface<DataIsNull> {
   int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
   constexpr ContIter begin() const { return ContIter(buff); }
   constexpr ContIter end() const { return ContIter(buff + 8); }
-  constexpr const int *data() const { return nullptr; }
+  constexpr const int* data() const { return nullptr; }
 };
 static_assert(std::ranges::view<DataIsNull>);
 
@@ -142,13 +138,21 @@ struct BoolConvertibleComparison : std::ranges::view_interface<BoolConvertibleCo
   };
 
   struct SentinelType {
-    int *base_;
+    int* base_;
     explicit SentinelType() = default;
-    constexpr explicit SentinelType(int *base) : base_(base) {}
-    friend constexpr ResultType operator==(ForwardIter const& iter, SentinelType const& sent) noexcept { return {base(iter) == sent.base_}; }
-    friend constexpr ResultType operator==(SentinelType const& sent, ForwardIter const& iter) noexcept { return {base(iter) == sent.base_}; }
-    friend constexpr ResultType operator!=(ForwardIter const& iter, SentinelType const& sent) noexcept { return {base(iter) != sent.base_}; }
-    friend constexpr ResultType operator!=(SentinelType const& sent, ForwardIter const& iter) noexcept { return {base(iter) != sent.base_}; }
+    constexpr explicit SentinelType(int* base) : base_(base) {}
+    friend constexpr ResultType operator==(ForwardIter const& iter, SentinelType const& sent) noexcept {
+      return {base(iter) == sent.base_};
+    }
+    friend constexpr ResultType operator==(SentinelType const& sent, ForwardIter const& iter) noexcept {
+      return {base(iter) == sent.base_};
+    }
+    friend constexpr ResultType operator!=(ForwardIter const& iter, SentinelType const& sent) noexcept {
+      return {base(iter) != sent.base_};
+    }
+    friend constexpr ResultType operator!=(SentinelType const& sent, ForwardIter const& iter) noexcept {
+      return {base(iter) != sent.base_};
+    }
   };
 
   int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
@@ -157,21 +161,21 @@ struct BoolConvertibleComparison : std::ranges::view_interface<BoolConvertibleCo
 };
 static_assert(std::ranges::view<BoolConvertibleComparison>);
 
-template<class T>
-concept EmptyInvocable = requires (T const& obj) { obj.empty(); };
+template <class T>
+concept EmptyInvocable = requires(T const& obj) { obj.empty(); };
 
-template<class T>
-concept BoolOpInvocable = requires (T const& obj) { bool(obj); };
+template <class T>
+concept BoolOpInvocable = requires(T const& obj) { bool(obj); };
 
 constexpr bool testEmpty() {
   static_assert(!EmptyInvocable<InputRange>);
   // LWG 3715: `view_interface::empty` is overconstrained
   static_assert(EmptyInvocable<SizedInputRange>);
-  static_assert( EmptyInvocable<ForwardRange>);
+  static_assert(EmptyInvocable<ForwardRange>);
 
   static_assert(!BoolOpInvocable<InputRange>);
   static_assert(BoolOpInvocable<SizedInputRange>);
-  static_assert( BoolOpInvocable<ForwardRange>);
+  static_assert(BoolOpInvocable<ForwardRange>);
 
   SizedInputRange sizedInputRange;
   assert(!sizedInputRange.empty());
@@ -224,12 +228,12 @@ constexpr bool testEmpty() {
   return true;
 }
 
-template<class T>
-concept DataInvocable = requires (T const& obj) { obj.data(); };
+template <class T>
+concept DataInvocable = requires(T const& obj) { obj.data(); };
 
 constexpr bool testData() {
   static_assert(!DataInvocable<ForwardRange>);
-  static_assert( DataInvocable<ContRange>);
+  static_assert(DataInvocable<ContRange>);
 
   ContRange contiguous;
   assert(contiguous.data() == contiguous.buff);
@@ -249,18 +253,18 @@ constexpr bool testData() {
   return true;
 }
 
-template<class T>
-concept SizeInvocable = requires (T const& obj) { obj.size(); };
+template <class T>
+concept SizeInvocable = requires(T const& obj) { obj.size(); };
 
 constexpr bool testSize() {
   static_assert(!SizeInvocable<InputRange>);
   static_assert(!SizeInvocable<NotSizedSentinel>);
-  static_assert( SizeInvocable<ForwardRange>);
+  static_assert(SizeInvocable<ForwardRange>);
 
   // Test the test.
   static_assert(std::same_as<decltype(std::declval<ForwardIter>() - std::declval<ForwardIter>()), std::ptrdiff_t>);
   using UnsignedSize = std::make_unsigned_t<std::ptrdiff_t>;
-  using SignedSize = std::common_type_t<std::ptrdiff_t, std::make_signed_t<UnsignedSize>>;
+  using SignedSize   = std::common_type_t<std::ptrdiff_t, std::make_signed_t<UnsignedSize>>;
   ForwardRange forwardRange;
   assert(forwardRange.size() == 8);
   assert(static_cast<ForwardRange const&>(forwardRange).size() == 8);
@@ -284,12 +288,12 @@ constexpr bool testSize() {
   return true;
 }
 
-template<class T>
-concept SubscriptInvocable = requires (T const& obj, std::size_t n) { obj[n]; };
+template <class T>
+concept SubscriptInvocable = requires(T const& obj, std::size_t n) { obj[n]; };
 
 constexpr bool testSubscript() {
   static_assert(!SubscriptInvocable<ForwardRange>);
-  static_assert( SubscriptInvocable<RARange>);
+  static_assert(SubscriptInvocable<RARange>);
 
   RARange randomAccess;
   assert(randomAccess[2] == 2);
@@ -300,17 +304,57 @@ constexpr bool testSubscript() {
   return true;
 }
 
-template<class T>
-concept FrontInvocable = requires (T const& obj) { obj.front(); };
+template <class T>
+concept AtInvocable = requires(T const& obj, std::size_t n) { obj.at(n); };
+
+constexpr bool testAt() {
+  static_assert(!AtInvocable<ForwardRange>);
+  static_assert(AtInvocable<RARange>);
+
+  RARange randomAccess;
+  assert(randomAccess.at(2) == 2);
+  assert(static_cast<RARange const&>(randomAccess).at(2) == 2);
+  randomAccess.at(2) = 3;
+  assert(randomAccess.at(2) == 3);
+
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  if (!std::is_constant_evaluated()) {
+    try {
+      TEST_IGNORE_NODISCARD randomAccess.at(0);
+      assert(true);
+    } catch (std::out_of_range const&) {
+      // pass
+      assert(false); // This should not be reached because index 0 is valid.
+    } catch (...) {
+      assert(false);
+    }
+
+    // Test that at() throws an exception when the index is out of range.
+    try {
+      TEST_IGNORE_NODISCARD randomAccess.at(10);
+      assert(false);
+    } catch (std::out_of_range const&) {
+      // pass
+    } catch (...) {
+      assert(false);
+    }
+  }
+#endif
+
+  return true;
+}
+
+template <class T>
+concept FrontInvocable = requires(T const& obj) { obj.front(); };
 
-template<class T>
-concept BackInvocable = requires (T const& obj) { obj.back(); };
+template <class T>
+concept BackInvocable = requires(T const& obj) { obj.back(); };
 
 constexpr bool testFrontBack() {
   static_assert(!FrontInvocable<InputRange>);
-  static_assert( FrontInvocable<ForwardRange>);
+  static_assert(FrontInvocable<ForwardRange>);
   static_assert(!BackInvocable<ForwardRange>);
-  static_assert( BackInvocable<RARange>);
+  static_assert(BackInvocable<RARange>);
 
   ForwardRange forwardRange;
   assert(forwardRange.front() == 0);
@@ -332,8 +376,10 @@ constexpr bool testFrontBack() {
   return true;
 }
 
-struct V1 : std::ranges::view_interface<V1> { };
-struct V2 : std::ranges::view_interface<V2> { V1 base_; };
+struct V1 : std::ranges::view_interface<V1> {};
+struct V2 : std::ranges::view_interface<V2> {
+  V1 base_;
+};
 static_assert(sizeof(V2) == sizeof(V1));
 
 int main(int, char**) {
@@ -349,6 +395,9 @@ int main(int, char**) {
   testSubscript();
   static_assert(testSubscript());
 
+  testAt();
+  static_assert(testAt());
+
   testFrontBack();
   static_assert(testFrontBack());
 
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index d764a1f677ba6..3934e101073a0 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1522,6 +1522,13 @@ def add_version_header(tc):
             },
             "headers": ["variant"],
         },
+        {
+            "name": "__cpp_lib_view_interface",
+            "values": {
+                "c++20": 202606,
+            },
+            "headers": ["ranges"],
+        },
         {
             "name": "__cpp_lib_void_t",
             "values": {"c++17": 201411},



More information about the libcxx-commits mailing list