diff options
author | Arthur O'Dwyer <arthur.j.odwyer@gmail.com> | 2021-12-06 15:39:08 -0500 |
---|---|---|
committer | Arthur O'Dwyer <arthur.j.odwyer@gmail.com> | 2021-12-07 13:59:41 -0500 |
commit | bd0c0e5b8c8c0bff5ffdff3d1d43dfbacf9caa06 (patch) | |
tree | 3113e42d4065dda46433019d462f91295355734f | |
parent | 7a06a14f624d31e7ed920f254d8b733d535258cb (diff) | |
download | llvm-bd0c0e5b8c8c0bff5ffdff3d1d43dfbacf9caa06.tar.gz |
[libc++] [ranges] SFINAE-friendly "write it three times" in views::counted.
Before this patch, the new test's `CountedInvocable<int*, int*>`
would hard-error instead of SFINAEing and cleanly returning false.
Notice that views::counted specifically does NOT work with pipes;
`counted(42)` is ill-formed. This is because `counted`'s first argument
is supposed to be an iterator, not a range.
Also, mark `views::counted(it, n)` as [[nodiscard]], and test that.
(We have a general policy now that range adaptors are consistently
marked [[nodiscard]], so that people don't accidentally think that
they have side effects. This matters mostly for `reverse` and
`transform`, arguably `drop`, and just generally let's be consistent.)
Differential Revision: https://reviews.llvm.org/D115177
4 files changed, 241 insertions, 197 deletions
diff --git a/libcxx/include/__ranges/counted.h b/libcxx/include/__ranges/counted.h index d292bcbb1849..cb9784092420 100644 --- a/libcxx/include/__ranges/counted.h +++ b/libcxx/include/__ranges/counted.h @@ -9,6 +9,7 @@ #ifndef _LIBCPP___RANGES_COUNTED_H #define _LIBCPP___RANGES_COUNTED_H +#include <__concepts/convertible_to.h> #include <__config> #include <__iterator/concepts.h> #include <__iterator/counted_iterator.h> @@ -16,10 +17,7 @@ #include <__iterator/incrementable_traits.h> #include <__iterator/iterator_traits.h> #include <__memory/pointer_traits.h> -#include <__ranges/concepts.h> #include <__ranges/subrange.h> -#include <__utility/decay_copy.h> -#include <__utility/declval.h> #include <__utility/forward.h> #include <__utility/move.h> #include <span> @@ -36,50 +34,39 @@ _LIBCPP_BEGIN_NAMESPACE_STD namespace ranges::views { namespace __counted { - template<class _From, class _To> - concept __explicitly_convertible = requires { - _To(_From{}); - }; struct __fn { - template<class _Iter, class _Diff> - requires contiguous_iterator<decay_t<_Iter>> && - __explicitly_convertible<_Diff, iter_difference_t<_Iter>> + template<contiguous_iterator _It> _LIBCPP_HIDE_FROM_ABI - constexpr auto operator()(_Iter&& __it, _Diff __c) const - noexcept(noexcept( - span(_VSTD::to_address(__it), static_cast<iter_difference_t<_Iter>>(__c)) - )) - { - return span(_VSTD::to_address(__it), static_cast<iter_difference_t<_Iter>>(__c)); - } - - template<class _Iter, class _Diff> - requires random_access_iterator<decay_t<_Iter>> && - __explicitly_convertible<_Diff, iter_difference_t<_Iter>> + static constexpr auto __go(_It __it, iter_difference_t<_It> __count) + noexcept(noexcept(span(_VSTD::to_address(__it), static_cast<size_t>(__count)))) + // Deliberately omit return-type SFINAE, because to_address is not SFINAE-friendly + { return span(_VSTD::to_address(__it), static_cast<size_t>(__count)); } + + template<random_access_iterator _It> _LIBCPP_HIDE_FROM_ABI - constexpr auto operator()(_Iter&& __it, _Diff __c) const - noexcept( - noexcept(__it + static_cast<iter_difference_t<_Iter>>(__c)) && - noexcept(ranges::subrange(_VSTD::forward<_Iter>(__it), _VSTD::__decay_copy(__it))) - ) - { - auto __last = __it + static_cast<iter_difference_t<_Iter>>(__c); - return ranges::subrange(_VSTD::forward<_Iter>(__it), _VSTD::move(__last)); - } - - template<class _Iter, class _Diff> - requires __explicitly_convertible<_Diff, iter_difference_t<_Iter>> + static constexpr auto __go(_It __it, iter_difference_t<_It> __count) + noexcept(noexcept(subrange(__it, __it + __count))) + -> decltype( subrange(__it, __it + __count)) + { return subrange(__it, __it + __count); } + + template<class _It> _LIBCPP_HIDE_FROM_ABI - constexpr auto operator()(_Iter&& __it, _Diff __c) const - noexcept(noexcept( - ranges::subrange(counted_iterator(_VSTD::forward<_Iter>(__it), __c), default_sentinel) - )) - { - return ranges::subrange(counted_iterator(_VSTD::forward<_Iter>(__it), __c), default_sentinel); - } + static constexpr auto __go(_It __it, iter_difference_t<_It> __count) + noexcept(noexcept(subrange(counted_iterator(_VSTD::move(__it), __count), default_sentinel))) + -> decltype( subrange(counted_iterator(_VSTD::move(__it), __count), default_sentinel)) + { return subrange(counted_iterator(_VSTD::move(__it), __count), default_sentinel); } + + template<class _It, convertible_to<iter_difference_t<_It>> _Diff> + requires input_or_output_iterator<decay_t<_It>> + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_It&& __it, _Diff&& __count) const + noexcept(noexcept(__go(_VSTD::forward<_It>(__it), _VSTD::forward<_Diff>(__count)))) + -> decltype( __go(_VSTD::forward<_It>(__it), _VSTD::forward<_Diff>(__count))) + { return __go(_VSTD::forward<_It>(__it), _VSTD::forward<_Diff>(__count)); } }; -} + +} // namespace __counted inline namespace __cpo { inline constexpr auto counted = __counted::__fn{}; diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.counted/adaptor.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.counted/adaptor.nodiscard.verify.cpp new file mode 100644 index 000000000000..dc2c8bafb1b0 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.counted/adaptor.nodiscard.verify.cpp @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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: libcpp-has-no-incomplete-ranges + +// Test the libc++ extension that std::views::counted is marked as [[nodiscard]]. + +#include <ranges> + +void test() { + int range[] = {1, 2, 3}; + + std::views::counted(range, 1); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.counted/counted.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.counted/counted.pass.cpp index 2ebd7036db33..29a891174b58 100644 --- a/libcxx/test/std/ranges/range.adaptors/range.counted/counted.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.counted/counted.pass.cpp @@ -12,191 +12,225 @@ // std::views::counted; -#include <concepts> #include <ranges> +#include <cassert> +#include <concepts> +#include <cstddef> +#include <memory> #include <span> +#include <utility> -#include <cassert> #include "test_macros.h" #include "test_iterators.h" -struct Unrelated {}; +struct RvalueConvertible { + RvalueConvertible(const RvalueConvertible&) = delete; + operator int() &&; +}; -struct ConvertibleToSize { - constexpr operator std::ptrdiff_t() const { return 8; } +struct LvalueConvertible { + LvalueConvertible(const LvalueConvertible&) = delete; + operator int() &; }; -struct ImplicitlyConvertible { - operator short(); - explicit operator std::ptrdiff_t() = delete; +struct OnlyExplicitlyConvertible { + explicit operator int() const; }; -template<class Iter, class T> -concept CountedInvocable = requires(Iter& i, T t) { std::views::counted(i, t); }; +template<class... Ts> +concept CountedInvocable = requires (Ts&&... ts) { + std::views::counted(std::forward<Ts>(ts)...); +}; constexpr bool test() { int buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8}; { - static_assert( CountedInvocable<contiguous_iterator<int*>, ConvertibleToSize>); - static_assert(!CountedInvocable<contiguous_iterator<int*>, ImplicitlyConvertible>); - static_assert(!CountedInvocable<contiguous_iterator<int*>, Unrelated>); + static_assert(std::addressof(std::views::counted) == std::addressof(std::ranges::views::counted)); + + auto copy = std::views::counted; + static_assert(std::semiregular<decltype(copy)>); + + static_assert( CountedInvocable<int*, size_t>); + static_assert(!CountedInvocable<int*, LvalueConvertible>); + static_assert( CountedInvocable<int*, LvalueConvertible&>); + static_assert( CountedInvocable<int*, RvalueConvertible>); + static_assert(!CountedInvocable<int*, RvalueConvertible&>); + static_assert(!CountedInvocable<int*, OnlyExplicitlyConvertible>); + static_assert(!CountedInvocable<int*, int*>); + static_assert(!CountedInvocable<int*>); + static_assert(!CountedInvocable<size_t>); + static_assert(!CountedInvocable<>); + } + + { + auto c1 = std::views::counted(buffer, 3); + auto c2 = std::views::counted(std::as_const(buffer), 3); + + ASSERT_SAME_TYPE(decltype(c1), std::span<int>); + ASSERT_SAME_TYPE(decltype(c2), std::span<const int>); + + assert(c1.data() == buffer && c1.size() == 3); + assert(c2.data() == buffer && c2.size() == 3); + } + + { + auto it = contiguous_iterator<int*>(buffer); + auto cit = contiguous_iterator<const int*>(buffer); + + auto c1 = std::views::counted(it, 3); + auto c2 = std::views::counted(std::as_const(it), 3); + auto c3 = std::views::counted(std::move(it), 3); + auto c4 = std::views::counted(contiguous_iterator<int*>(buffer), 3); + auto c5 = std::views::counted(cit, 3); + auto c6 = std::views::counted(std::as_const(cit), 3); + auto c7 = std::views::counted(std::move(cit), 3); + auto c8 = std::views::counted(contiguous_iterator<const int*>(buffer), 3); + + ASSERT_SAME_TYPE(decltype(c1), std::span<int>); + ASSERT_SAME_TYPE(decltype(c2), std::span<int>); + ASSERT_SAME_TYPE(decltype(c3), std::span<int>); + ASSERT_SAME_TYPE(decltype(c4), std::span<int>); + ASSERT_SAME_TYPE(decltype(c5), std::span<const int>); + ASSERT_SAME_TYPE(decltype(c6), std::span<const int>); + ASSERT_SAME_TYPE(decltype(c7), std::span<const int>); + ASSERT_SAME_TYPE(decltype(c8), std::span<const int>); + + assert(c1.data() == buffer && c1.size() == 3); + assert(c2.data() == buffer && c2.size() == 3); + assert(c3.data() == buffer && c3.size() == 3); + assert(c4.data() == buffer && c4.size() == 3); + assert(c5.data() == buffer && c5.size() == 3); + assert(c6.data() == buffer && c6.size() == 3); + assert(c7.data() == buffer && c7.size() == 3); + assert(c8.data() == buffer && c8.size() == 3); + } - static_assert(std::semiregular<std::remove_const_t<decltype(std::views::counted)>>); + { + auto it = random_access_iterator<int*>(buffer); + auto cit = random_access_iterator<const int*>(buffer); + + auto c1 = std::views::counted(it, 3); + auto c2 = std::views::counted(std::as_const(it), 3); + auto c3 = std::views::counted(std::move(it), 3); + auto c4 = std::views::counted(random_access_iterator<int*>(buffer), 3); + auto c5 = std::views::counted(cit, 3); + auto c6 = std::views::counted(std::as_const(cit), 3); + auto c7 = std::views::counted(std::move(cit), 3); + auto c8 = std::views::counted(random_access_iterator<const int*>(buffer), 3); + + ASSERT_SAME_TYPE(decltype(c1), std::ranges::subrange<random_access_iterator<int*>>); + ASSERT_SAME_TYPE(decltype(c2), std::ranges::subrange<random_access_iterator<int*>>); + ASSERT_SAME_TYPE(decltype(c3), std::ranges::subrange<random_access_iterator<int*>>); + ASSERT_SAME_TYPE(decltype(c4), std::ranges::subrange<random_access_iterator<int*>>); + ASSERT_SAME_TYPE(decltype(c5), std::ranges::subrange<random_access_iterator<const int*>>); + ASSERT_SAME_TYPE(decltype(c6), std::ranges::subrange<random_access_iterator<const int*>>); + ASSERT_SAME_TYPE(decltype(c7), std::ranges::subrange<random_access_iterator<const int*>>); + ASSERT_SAME_TYPE(decltype(c8), std::ranges::subrange<random_access_iterator<const int*>>); + + assert(c1.begin() == it && c1.end() == it + 3); + assert(c2.begin() == it && c2.end() == it + 3); + assert(c3.begin() == it && c3.end() == it + 3); + assert(c4.begin() == it && c4.end() == it + 3); + assert(c5.begin() == cit && c5.end() == cit + 3); + assert(c6.begin() == cit && c6.end() == cit + 3); + assert(c7.begin() == cit && c7.end() == cit + 3); + assert(c8.begin() == cit && c8.end() == cit + 3); } { - { - contiguous_iterator<int*> iter(buffer); - std::span<int> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.data() == buffer); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<int>); - } - { - const contiguous_iterator<int*> iter(buffer); - std::span<int> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.data() == buffer); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<int>); - } - { - contiguous_iterator<const int*> iter(buffer); - std::span<const int> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.data() == buffer); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<const int>); - } - { - const contiguous_iterator<const int*> iter(buffer); - std::span<const int> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.data() == buffer); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<const int>); - } + auto it = bidirectional_iterator<int*>(buffer); + auto cit = bidirectional_iterator<const int*>(buffer); + + auto c1 = std::views::counted(it, 3); + auto c2 = std::views::counted(std::as_const(it), 3); + auto c3 = std::views::counted(std::move(it), 3); + auto c4 = std::views::counted(bidirectional_iterator<int*>(buffer), 3); + auto c5 = std::views::counted(cit, 3); + auto c6 = std::views::counted(std::as_const(cit), 3); + auto c7 = std::views::counted(std::move(cit), 3); + auto c8 = std::views::counted(bidirectional_iterator<const int*>(buffer), 3); + + using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>; + using ConstExpected = std::ranges::subrange<std::counted_iterator<decltype(cit)>, std::default_sentinel_t>; + + ASSERT_SAME_TYPE(decltype(c1), Expected); + ASSERT_SAME_TYPE(decltype(c2), Expected); + ASSERT_SAME_TYPE(decltype(c3), Expected); + ASSERT_SAME_TYPE(decltype(c4), Expected); + ASSERT_SAME_TYPE(decltype(c5), ConstExpected); + ASSERT_SAME_TYPE(decltype(c6), ConstExpected); + ASSERT_SAME_TYPE(decltype(c7), ConstExpected); + ASSERT_SAME_TYPE(decltype(c8), ConstExpected); + + assert(c1.begin().base() == it && c1.size() == 3); + assert(c2.begin().base() == it && c2.size() == 3); + assert(c3.begin().base() == it && c3.size() == 3); + assert(c4.begin().base() == it && c4.size() == 3); + assert(c5.begin().base() == cit && c5.size() == 3); + assert(c6.begin().base() == cit && c6.size() == 3); + assert(c7.begin().base() == cit && c7.size() == 3); + assert(c8.begin().base() == cit && c8.size() == 3); } { - { - random_access_iterator<int*> iter(buffer); - std::ranges::subrange<random_access_iterator<int*>> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == iter); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<int*>>); - } - { - const random_access_iterator<int*> iter(buffer); - std::ranges::subrange<random_access_iterator<int*>> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == iter); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<int*>>); - } - { - random_access_iterator<const int*> iter(buffer); - std::ranges::subrange<random_access_iterator<const int*>> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == iter); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<const int*>>); - } - { - const random_access_iterator<const int*> iter(buffer); - std::ranges::subrange<random_access_iterator<const int*>> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == iter); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<const int*>>); - } + auto it = output_iterator<int*>(buffer); + + auto c1 = std::views::counted(it, 3); + auto c2 = std::views::counted(std::as_const(it), 3); + auto c3 = std::views::counted(std::move(it), 3); + auto c4 = std::views::counted(output_iterator<int*>(buffer), 3); + + using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>; + + ASSERT_SAME_TYPE(decltype(c1), Expected); + ASSERT_SAME_TYPE(decltype(c2), Expected); + ASSERT_SAME_TYPE(decltype(c3), Expected); + ASSERT_SAME_TYPE(decltype(c4), Expected); + + assert(base(c1.begin().base()) == buffer && c1.size() == 3); + assert(base(c2.begin().base()) == buffer && c2.size() == 3); + assert(base(c3.begin().base()) == buffer && c3.size() == 3); + assert(base(c4.begin().base()) == buffer && c4.size() == 3); } { - { - bidirectional_iterator<int*> iter(buffer); - std::ranges::subrange< - std::counted_iterator<bidirectional_iterator<int*>>, - std::default_sentinel_t> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == std::counted_iterator(iter, 8)); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), - std::ranges::subrange< - std::counted_iterator<bidirectional_iterator<int*>>, - std::default_sentinel_t>); - } - { - const bidirectional_iterator<int*> iter(buffer); - std::ranges::subrange< - std::counted_iterator<bidirectional_iterator<int*>>, - std::default_sentinel_t> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == std::counted_iterator(iter, 8)); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), - std::ranges::subrange< - std::counted_iterator<bidirectional_iterator<int*>>, - std::default_sentinel_t>); - } - { - output_iterator<const int*> iter(buffer); - std::ranges::subrange< - std::counted_iterator<output_iterator<const int*>>, - std::default_sentinel_t> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == std::counted_iterator(iter, 8)); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), - std::ranges::subrange< - std::counted_iterator<output_iterator<const int*>>, - std::default_sentinel_t>); - } - { - const output_iterator<const int*> iter(buffer); - std::ranges::subrange< - std::counted_iterator<output_iterator<const int*>>, - std::default_sentinel_t> s = std::views::counted(iter, 8); - assert(s.size() == 8); - assert(s.begin() == std::counted_iterator(iter, 8)); - - ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), - std::ranges::subrange< - std::counted_iterator<output_iterator<const int*>>, - std::default_sentinel_t>); - } - { - cpp20_input_iterator<int*> iter(buffer); - std::ranges::subrange< - std::counted_iterator<cpp20_input_iterator<int*>>, - std::default_sentinel_t> s = std::views::counted(std::move(iter), 8); - assert(s.size() == 8); - assert(s.begin().base().base() == buffer); - - ASSERT_SAME_TYPE(decltype(std::views::counted(std::move(iter), 8)), - std::ranges::subrange< - std::counted_iterator<cpp20_input_iterator<int*>>, - std::default_sentinel_t>); - } - { - std::ranges::subrange< - std::counted_iterator<cpp20_input_iterator<int*>>, - std::default_sentinel_t> s = std::views::counted(cpp20_input_iterator<int*>(buffer), 8); - assert(s.size() == 8); - assert(s.begin().base().base() == buffer); - - ASSERT_SAME_TYPE(decltype(std::views::counted(cpp20_input_iterator<int*>(buffer), 8)), - std::ranges::subrange< - std::counted_iterator<cpp20_input_iterator<int*>>, - std::default_sentinel_t>); - } + auto it = cpp17_input_iterator<int*>(buffer); + + auto c1 = std::views::counted(it, 3); + auto c2 = std::views::counted(std::as_const(it), 3); + auto c3 = std::views::counted(std::move(it), 3); + auto c4 = std::views::counted(cpp17_input_iterator<int*>(buffer), 3); + + using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>; + + ASSERT_SAME_TYPE(decltype(c1), Expected); + ASSERT_SAME_TYPE(decltype(c2), Expected); + ASSERT_SAME_TYPE(decltype(c3), Expected); + ASSERT_SAME_TYPE(decltype(c4), Expected); + + assert(base(c1.begin().base()) == buffer && c1.size() == 3); + assert(base(c2.begin().base()) == buffer && c2.size() == 3); + assert(base(c3.begin().base()) == buffer && c3.size() == 3); + assert(base(c4.begin().base()) == buffer && c4.size() == 3); } { - static_assert(std::same_as<decltype(std::views::counted), decltype(std::ranges::views::counted)>); + auto it = cpp20_input_iterator<int*>(buffer); + + static_assert(!std::copyable<cpp20_input_iterator<int*>>); + static_assert(!CountedInvocable<cpp20_input_iterator<int*>&, int>); + static_assert(!CountedInvocable<const cpp20_input_iterator<int*>&, int>); + auto c3 = std::views::counted(std::move(it), 3); + auto c4 = std::views::counted(cpp20_input_iterator<int*>(buffer), 3); + + using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>; + + ASSERT_SAME_TYPE(decltype(c3), Expected); + ASSERT_SAME_TYPE(decltype(c4), Expected); + + assert(base(c3.begin().base()) == buffer && c3.size() == 3); + assert(base(c4.begin().base()) == buffer && c4.size() == 3); } return true; diff --git a/libcxx/test/support/test_iterators.h b/libcxx/test/support/test_iterators.h index d69ceec66066..ef699642cb95 100644 --- a/libcxx/test/support/test_iterators.h +++ b/libcxx/test/support/test_iterators.h @@ -657,6 +657,8 @@ struct cpp20_input_iterator { constexpr I base() && { return std::move(base_); } + friend constexpr I base(const cpp20_input_iterator& i) { return i.base_; } + template <class T> void operator,(T const &) = delete; |