[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