[libcxx-commits] [libcxx] 5671ff2 - [libcxx] Implement view.interface.

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jun 1 12:35:20 PDT 2021


Author: zoecarver
Date: 2021-06-01T12:34:47-07:00
New Revision: 5671ff20d92b17d9b95cb5c9e08cc109fe77844e

URL: https://github.com/llvm/llvm-project/commit/5671ff20d92b17d9b95cb5c9e08cc109fe77844e
DIFF: https://github.com/llvm/llvm-project/commit/5671ff20d92b17d9b95cb5c9e08cc109fe77844e.diff

LOG: [libcxx] Implement view.interface.

This will unblock work on ranges::view. Based on D101396.

Refs http://eel.is/c++draft/view.interface.

Differential Revision: https://reviews.llvm.org/D101737

Added: 
    libcxx/include/__ranges/view_interface.h
    libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp

Modified: 
    libcxx/docs/OneRangesProposalStatus.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/ranges

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/OneRangesProposalStatus.csv b/libcxx/docs/OneRangesProposalStatus.csv
index e7f2cf8df31f0..0917282c70dd2 100644
--- a/libcxx/docs/OneRangesProposalStatus.csv
+++ b/libcxx/docs/OneRangesProposalStatus.csv
@@ -44,7 +44,7 @@ bidirectional_iterator: `D100278 <https://llvm.org/D100278>`_",
 [range.refinements],"OutputRange, InputRange, ForwardRange, BidirectionalRange, RandomAccessRange, ContiguousRange, CommonRange, ViewableRange","[ranges.syn]: pt. 2, [range.range]",Christopher Di Bella,"input_range: `D100271 <https://llvm.org/D100271>`_
 forward_range: `D100275 <https://llvm.org/D100275>`_
 bidirectional_range: `D100278 <https://llvm.org/D100278>`_",
-[view.interface],[range.utility.helpers] and view_interface,"[ranges.syn]: pt. 2, [range.view], [range.iterator.operations.prev], [range.refinements]",Zoe Carver,`D101737 <https://llvm.org/D101737>`_,
+[view.interface],[range.utility.helpers] and view_interface,"[ranges.syn]: pt. 2, [range.view], [range.iterator.operations.prev], [range.refinements]",Zoe Carver,`D101737 <https://llvm.org/D101737>`_,✅
 [range.subrange],,[view.interface],Zoe Carver,`D102006 <https://llvm.org/D102006>`_,
 [range.all],view::all,"[range.subrange], [range.view.ref]",Zoe Carver,`D102028 <https://llvm.org/D102028>`_,
 [range.view.ref],ref-view,[view.interface],Zoe Carver,`D102020 <https://llvm.org/D102020>`_,

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index e5ae54b225a8a..89b996671b23e 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -48,6 +48,7 @@ set(files
   __ranges/data.h
   __ranges/empty.h
   __ranges/enable_borrowed_range.h
+  __ranges/view_interface.h
   __ranges/view.h
   __ranges/size.h
   __split_buffer

diff  --git a/libcxx/include/__ranges/view_interface.h b/libcxx/include/__ranges/view_interface.h
new file mode 100644
index 0000000000000..ffaab1d85d202
--- /dev/null
+++ b/libcxx/include/__ranges/view_interface.h
@@ -0,0 +1,181 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#ifndef _LIBCPP___RANGES_VIEW_INTERFACE_H
+#define _LIBCPP___RANGES_VIEW_INTERFACE_H
+
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__iterator/prev.h>
+#include <__ranges/access.h>
+#include <__ranges/empty.h>
+#include <__ranges/view.h>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_RANGES)
+
+namespace ranges {
+
+template<class _Tp>
+concept __can_empty = requires(_Tp __t) { ranges::empty(__t); };
+
+template<class _Tp>
+void __implicitly_convert_to(type_identity_t<_Tp>) noexcept;
+
+template<class _Derived>
+  requires is_class_v<_Derived> && same_as<_Derived, remove_cv_t<_Derived>>
+class view_interface : public view_base {
+  constexpr _Derived& __derived() noexcept {
+    return static_cast<_Derived&>(*this);
+  }
+
+  constexpr _Derived const& __derived() const noexcept {
+    return static_cast<_Derived const&>(*this);
+  }
+
+public:
+  template<class _D2 = _Derived>
+  [[nodiscard]] constexpr bool empty()
+    noexcept(noexcept(__implicitly_convert_to<bool>(ranges::begin(__derived()) == ranges::end(__derived()))))
+    requires forward_range<_D2>
+  {
+    return ranges::begin(__derived()) == ranges::end(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  [[nodiscard]] constexpr bool empty() const
+    noexcept(noexcept(__implicitly_convert_to<bool>(ranges::begin(__derived()) == ranges::end(__derived()))))
+    requires forward_range<const _D2>
+  {
+    return ranges::begin(__derived()) == ranges::end(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  constexpr explicit operator bool()
+    noexcept(noexcept(ranges::empty(declval<_D2>())))
+    requires __can_empty<_D2>
+  {
+    return !ranges::empty(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  constexpr explicit operator bool() const
+    noexcept(noexcept(ranges::empty(declval<const _D2>())))
+    requires __can_empty<const _D2>
+  {
+    return !ranges::empty(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  constexpr auto data()
+    noexcept(noexcept(_VSTD::to_address(ranges::begin(__derived()))))
+    requires contiguous_iterator<iterator_t<_D2>>
+  {
+    return _VSTD::to_address(ranges::begin(__derived()));
+  }
+
+  template<class _D2 = _Derived>
+  constexpr auto data() const
+    noexcept(noexcept(_VSTD::to_address(ranges::begin(__derived()))))
+    requires range<const _D2> && contiguous_iterator<iterator_t<const _D2>>
+  {
+    return _VSTD::to_address(ranges::begin(__derived()));
+  }
+
+  template<class _D2 = _Derived>
+  constexpr auto size()
+    noexcept(noexcept(ranges::end(__derived()) - ranges::begin(__derived())))
+    requires forward_range<_D2>
+      && sized_sentinel_for<sentinel_t<_D2>, iterator_t<_D2>>
+  {
+    return ranges::end(__derived()) - ranges::begin(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  constexpr auto size() const
+    noexcept(noexcept(ranges::end(__derived()) - ranges::begin(__derived())))
+    requires forward_range<const _D2>
+      && sized_sentinel_for<sentinel_t<const _D2>, iterator_t<const _D2>>
+  {
+    return ranges::end(__derived()) - ranges::begin(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  constexpr decltype(auto) front()
+    noexcept(noexcept(*ranges::begin(__derived())))
+    requires forward_range<_D2>
+  {
+    _LIBCPP_ASSERT(!empty(),
+        "Precondition `!empty()` not satisfied. `.front()` called on an empty view.");
+    return *ranges::begin(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  constexpr decltype(auto) front() const
+    noexcept(noexcept(*ranges::begin(__derived())))
+    requires forward_range<const _D2>
+  {
+    _LIBCPP_ASSERT(!empty(),
+        "Precondition `!empty()` not satisfied. `.front()` called on an empty view.");
+    return *ranges::begin(__derived());
+  }
+
+  template<class _D2 = _Derived>
+  constexpr decltype(auto) back()
+    noexcept(noexcept(*ranges::prev(ranges::end(__derived()))))
+    requires bidirectional_range<_D2> && common_range<_D2>
+  {
+    _LIBCPP_ASSERT(!empty(),
+        "Precondition `!empty()` not satisfied. `.back()` called on an empty view.");
+    return *ranges::prev(ranges::end(__derived()));
+  }
+
+  template<class _D2 = _Derived>
+  constexpr decltype(auto) back() const
+    noexcept(noexcept(*ranges::prev(ranges::end(__derived()))))
+    requires bidirectional_range<const _D2> && common_range<const _D2>
+  {
+    _LIBCPP_ASSERT(!empty(),
+        "Precondition `!empty()` not satisfied. `.back()` called on an empty view.");
+    return *ranges::prev(ranges::end(__derived()));
+  }
+
+  template<random_access_range _RARange = _Derived>
+  constexpr decltype(auto) operator[](range_
diff erence_t<_RARange> __index)
+    noexcept(noexcept(ranges::begin(__derived())[__index]))
+  {
+    return ranges::begin(__derived())[__index];
+  }
+
+  template<random_access_range _RARange = const _Derived>
+  constexpr decltype(auto) operator[](range_
diff erence_t<_RARange> __index) const
+    noexcept(noexcept(ranges::begin(__derived())[__index]))
+  {
+    return ranges::begin(__derived())[__index];
+  }
+};
+
+}
+
+#endif // !defined(_LIBCPP_HAS_NO_RANGES)
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_VIEW_INTERFACE_H

diff  --git a/libcxx/include/ranges b/libcxx/include/ranges
index 3d5a833cec331..5bbe9f73af27b 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -78,6 +78,11 @@ namespace std::ranges {
 
   template <class _Tp>
     concept common_range = see below;
+
+  // [view.interface], class template view_interface
+  template<class D>
+    requires is_class_v<D> && same_as<D, remove_cv_t<D>>
+  class view_interface;
 }
 
 */
@@ -89,6 +94,7 @@ namespace std::ranges {
 #include <__ranges/empty.h>
 #include <__ranges/enable_borrowed_range.h>
 #include <__ranges/size.h>
+#include <__ranges/view_interface.h>
 #include <__ranges/view.h>
 #include <compare>          // Required by the standard.
 #include <initializer_list> // Required by the standard.

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
new file mode 100644
index 0000000000000..5e5cf707fe241
--- /dev/null
+++ b/libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp
@@ -0,0 +1,316 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+
+// template<class D>
+//   requires is_class_v<D> && same_as<D, remove_cv_t<D>>
+// class view_interface;
+
+#include <ranges>
+
+#include <cassert>
+#include "test_macros.h"
+#include "test_iterators.h"
+
+template<class T>
+concept ValidViewInterfaceType = requires { typename std::ranges::view_interface<T>; };
+
+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(std::derived_from<std::ranges::view_interface<Empty>, std::ranges::view_base>);
+
+using InputIter = cpp20_input_iterator<const int*>;
+
+struct InputRange : std::ranges::view_interface<InputRange> {
+  int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  constexpr InputIter begin() const { return InputIter(buff); }
+  constexpr InputIter end() const { return InputIter(buff + 8); }
+};
+
+struct NotSizedSentinel {
+  using I = int*;
+  using value_type = std::iter_value_t<I>;
+  using 
diff erence_type = std::iter_
diff erence_t<I>;
+  using iterator_concept = std::forward_iterator_tag;
+
+  NotSizedSentinel() = default;
+  explicit constexpr NotSizedSentinel(I);
+
+  constexpr int &operator*() const { return *value; };
+  NotSizedSentinel& operator++();
+  NotSizedSentinel operator++(int);
+  bool operator==(NotSizedSentinel const&) const;
+
+  int *value;
+};
+static_assert(std::forward_iterator<NotSizedSentinel>);
+
+using ForwardIter = forward_iterator<int*>;
+
+// So that we conform to sized_sentinel_for.
+constexpr std::ptr
diff _t operator-(const ForwardIter& x, const ForwardIter& y) {
+    return x.base() - y.base();
+}
+
+struct ForwardRange : std::ranges::view_interface<ForwardRange> {
+  int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  constexpr ForwardIter begin() const { return ForwardIter(const_cast<int*>(buff)); }
+  constexpr ForwardIter end() const { return ForwardIter(const_cast<int*>(buff) + 8); }
+};
+
+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() = default;
+  constexpr ForwardIter begin() const { return ForwardIter(const_cast<int*>(buff)); }
+  constexpr ForwardIter end() const { return ForwardIter(const_cast<int*>(buff) + 8); }
+};
+
+struct EmptyIsTrue : std::ranges::view_interface<EmptyIsTrue> {
+  int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  constexpr ForwardIter begin() const { return ForwardIter(const_cast<int*>(buff)); }
+  constexpr ForwardIter end() const { return ForwardIter(const_cast<int*>(buff) + 8); }
+  constexpr bool empty() const { return true; }
+};
+
+struct SizeIsTen : std::ranges::view_interface<SizeIsTen> {
+  int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  constexpr ForwardIter begin() const { return ForwardIter(const_cast<int*>(buff)); }
+  constexpr ForwardIter end() const { return ForwardIter(const_cast<int*>(buff) + 8); }
+  constexpr size_t size() const { return 10; }
+};
+
+using RAIter = random_access_iterator<int*>;
+
+struct RARange : std::ranges::view_interface<RARange> {
+  int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  constexpr RAIter begin() const { return RAIter(const_cast<int*>(buff)); }
+  constexpr RAIter end() const { return RAIter(const_cast<int*>(buff) + 8); }
+};
+
+using ContIter = contiguous_iterator<const int*>;
+
+struct ContRange : std::ranges::view_interface<ContRange> {
+  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); }
+};
+
+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; }
+};
+
+template<bool IsNoexcept>
+struct BoolConvertibleComparison : std::ranges::view_interface<BoolConvertibleComparison<IsNoexcept>> {
+  struct ResultType {
+    bool value;
+    constexpr operator bool() const noexcept(IsNoexcept) { return value; }
+  };
+
+  struct SentinelType {
+    int *base;
+    SentinelType() = default;
+    explicit constexpr SentinelType(int *base) : base(base) {}
+    friend constexpr ResultType operator==(ForwardIter const& iter, SentinelType const& sent) noexcept { return {iter.base() == sent.base}; }
+    friend constexpr ResultType operator==(SentinelType const& sent, ForwardIter const& iter) noexcept { return {iter.base() == sent.base}; }
+    friend constexpr ResultType operator!=(ForwardIter const& iter, SentinelType const& sent) noexcept { return {iter.base() != sent.base}; }
+    friend constexpr ResultType operator!=(SentinelType const& sent, ForwardIter const& iter) noexcept { return {iter.base() != sent.base}; }
+  };
+
+  int buff[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  constexpr ForwardIter begin() const noexcept { return ForwardIter(const_cast<int*>(buff)); }
+  constexpr SentinelType end() const noexcept { return SentinelType(const_cast<int*>(buff) + 8); }
+};
+
+template<class T>
+concept EmptyInvocable = requires (T const& obj) { obj.empty(); };
+
+template<class T>
+concept BoolOpInvocable = requires (T const& obj) { bool(obj); };
+
+constexpr bool testEmpty() {
+  static_assert(!EmptyInvocable<InputRange>);
+  static_assert( EmptyInvocable<ForwardRange>);
+
+  static_assert(!BoolOpInvocable<InputRange>);
+  static_assert( BoolOpInvocable<ForwardRange>);
+
+  ForwardRange forwardRange;
+  assert(!forwardRange.empty());
+  assert(!static_cast<ForwardRange const&>(forwardRange).empty());
+
+  assert(forwardRange);
+  assert(static_cast<ForwardRange const&>(forwardRange));
+
+  assert(!std::ranges::empty(forwardRange));
+  assert(!std::ranges::empty(static_cast<ForwardRange const&>(forwardRange)));
+
+  EmptyIsTrue emptyTrue;
+  assert(emptyTrue.empty());
+  assert(static_cast<EmptyIsTrue const&>(emptyTrue).empty());
+  assert(!emptyTrue.std::ranges::view_interface<EmptyIsTrue>::empty());
+
+  assert(!emptyTrue);
+  assert(!static_cast<EmptyIsTrue const&>(emptyTrue));
+  assert(!emptyTrue.std::ranges::view_interface<EmptyIsTrue>::operator bool());
+
+  assert(std::ranges::empty(emptyTrue));
+  assert(std::ranges::empty(static_cast<EmptyIsTrue const&>(emptyTrue)));
+
+  // Try calling empty on an rvalue.
+  MoveOnlyForwardRange moveOnly;
+  assert(!std::move(moveOnly).empty());
+
+  BoolConvertibleComparison<true> boolConv;
+  BoolConvertibleComparison<false> boolConv2;
+  static_assert(noexcept(boolConv.empty()));
+  static_assert(!noexcept(boolConv2.empty()));
+
+  assert(!boolConv.empty());
+  assert(!static_cast<BoolConvertibleComparison<true> const&>(boolConv).empty());
+
+  assert(boolConv);
+  assert(static_cast<BoolConvertibleComparison<true> const&>(boolConv));
+
+  assert(!std::ranges::empty(boolConv));
+  assert(!std::ranges::empty(static_cast<BoolConvertibleComparison<true> const&>(boolConv)));
+
+  return true;
+}
+
+template<class T>
+concept DataInvocable = requires (T const& obj) { obj.data(); };
+
+constexpr bool testData() {
+  static_assert(!DataInvocable<ForwardRange>);
+  static_assert( DataInvocable<ContRange>);
+
+  ContRange contiguous;
+  assert(contiguous.data() == contiguous.buff);
+  assert(static_cast<ContRange const&>(contiguous).data() == contiguous.buff);
+
+  assert(std::ranges::data(contiguous) == contiguous.buff);
+  assert(std::ranges::data(static_cast<ContRange const&>(contiguous)) == contiguous.buff);
+
+  DataIsNull dataNull;
+  assert(dataNull.data() == nullptr);
+  assert(static_cast<DataIsNull const&>(dataNull).data() == nullptr);
+  assert(dataNull.std::ranges::view_interface<DataIsNull>::data() == dataNull.buff);
+
+  assert(std::ranges::data(dataNull) == nullptr);
+  assert(std::ranges::data(static_cast<DataIsNull const&>(dataNull)) == nullptr);
+
+  return true;
+}
+
+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>);
+
+  ForwardRange forwardRange;
+  assert(forwardRange.size() == 8);
+  assert(static_cast<ForwardRange const&>(forwardRange).size() == 8);
+
+  assert(std::ranges::size(forwardRange) == 8);
+  assert(std::ranges::size(static_cast<ForwardRange const&>(forwardRange)) == 8);
+
+  SizeIsTen sizeTen;
+  assert(sizeTen.size() == 10);
+  assert(static_cast<SizeIsTen const&>(sizeTen).size() == 10);
+  assert(sizeTen.std::ranges::view_interface<SizeIsTen>::size() == 8);
+
+  assert(std::ranges::size(sizeTen) == 10);
+  assert(std::ranges::size(static_cast<SizeIsTen const&>(sizeTen)) == 10);
+
+  return true;
+}
+
+template<class T>
+concept SubscriptInvocable = requires (T const& obj, size_t n) { obj[n]; };
+
+constexpr bool testSubscript() {
+  static_assert(!SubscriptInvocable<ForwardRange>);
+  static_assert( SubscriptInvocable<RARange>);
+
+  RARange randomAccess;
+  assert(randomAccess[2] == 2);
+  assert(static_cast<RARange const&>(randomAccess)[2] == 2);
+  randomAccess[2] = 3;
+  assert(randomAccess[2] == 3);
+
+  return true;
+}
+
+template<class T>
+concept FrontInvocable = requires (T const& obj) { obj.front(); };
+
+template<class T>
+concept BackInvocable = requires (T const& obj) { obj.back(); };
+
+constexpr bool testFrontBack() {
+  static_assert(!FrontInvocable<InputRange>);
+  static_assert( FrontInvocable<ForwardRange>);
+  static_assert(!BackInvocable<ForwardRange>);
+  static_assert( BackInvocable<RARange>);
+
+  ForwardRange forwardRange;
+  assert(forwardRange.front() == 0);
+  assert(static_cast<ForwardRange const&>(forwardRange).front() == 0);
+  forwardRange.front() = 2;
+  assert(forwardRange.front() == 2);
+
+  RARange randomAccess;
+  assert(randomAccess.front() == 0);
+  assert(static_cast<RARange const&>(randomAccess).front() == 0);
+  randomAccess.front() = 2;
+  assert(randomAccess.front() == 2);
+
+  assert(randomAccess.back() == 7);
+  assert(static_cast<RARange const&>(randomAccess).back() == 7);
+  randomAccess.back() = 2;
+  assert(randomAccess.back() == 2);
+
+  return true;
+}
+
+int main(int, char**) {
+  testEmpty();
+  static_assert(testEmpty());
+
+  testData();
+  static_assert(testData());
+
+  testSize();
+  static_assert(testSize());
+
+  testSubscript();
+  static_assert(testSubscript());
+
+  testFrontBack();
+  static_assert(testFrontBack());
+
+  return 0;
+}


        


More information about the libcxx-commits mailing list