Skip to content

Commit 3959c95

Browse files
committed
[libc++] Add helper type non-propagating-cache
Differential Revision: https://reviews.llvm.org/D102121
1 parent 6f5064c commit 3959c95

13 files changed

+648
-49
lines changed

libcxx/include/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ set(files
193193
__ranges/empty.h
194194
__ranges/enable_borrowed_range.h
195195
__ranges/enable_view.h
196+
__ranges/non_propagating_cache.h
196197
__ranges/ref_view.h
197198
__ranges/size.h
198199
__ranges/subrange.h

libcxx/include/__ranges/drop_view.h

+10-49
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
#include <__ranges/all.h>
1818
#include <__ranges/concepts.h>
1919
#include <__ranges/enable_borrowed_range.h>
20+
#include <__ranges/non_propagating_cache.h>
2021
#include <__ranges/size.h>
2122
#include <__ranges/view_interface.h>
22-
#include <optional>
23+
#include <__utility/move.h>
24+
#include <concepts>
2325
#include <type_traits>
2426

2527
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -36,21 +38,16 @@ _LIBCPP_BEGIN_NAMESPACE_STD
3638
namespace ranges {
3739
template<view _View>
3840
class drop_view
39-
: public view_interface<drop_view<_View>> {
40-
41+
: public view_interface<drop_view<_View>>
42+
{
4143
// We cache begin() whenever ranges::next is not guaranteed O(1) to provide an
4244
// amortized O(1) begin() method. If this is an input_range, then we cannot cache
4345
// begin because begin is not equality preserving.
4446
// Note: drop_view<input-range>::begin() is still trivially amortized O(1) because
4547
// one can't call begin() on it more than once.
4648
static constexpr bool _UseCache = forward_range<_View> && !(random_access_range<_View> && sized_range<_View>);
47-
using _Cache = optional<iterator_t<_View>>;
48-
struct _Empty { };
49-
50-
// For forward ranges use std::optional to cache the begin iterator.
51-
// No unique address + _Empty means we don't use any extra space when this
52-
// is not a forward iterator.
53-
[[no_unique_address]] conditional_t<_UseCache, _Cache, _Empty> __cached_begin_;
49+
using _Cache = _If<_UseCache, __non_propagating_cache<iterator_t<_View>>, __empty_cache>;
50+
[[no_unique_address]] _Cache __cached_begin_ = _Cache();
5451
range_difference_t<_View> __count_ = 0;
5552
_View __base_ = _View();
5653

@@ -59,48 +56,12 @@ namespace ranges {
5956

6057
_LIBCPP_HIDE_FROM_ABI
6158
constexpr drop_view(_View __base, range_difference_t<_View> __count)
62-
: __cached_begin_()
63-
, __count_(__count)
59+
: __count_(__count)
6460
, __base_(_VSTD::move(__base))
6561
{
6662
_LIBCPP_ASSERT(__count_ >= 0, "count must be greater than or equal to zero.");
6763
}
6864

69-
_LIBCPP_HIDE_FROM_ABI
70-
constexpr drop_view(drop_view const& __other)
71-
: __cached_begin_() // Intentionally not propagating the cached begin iterator.
72-
, __count_(__other.__count_)
73-
, __base_(__other.__base_)
74-
{ }
75-
76-
_LIBCPP_HIDE_FROM_ABI
77-
constexpr drop_view(drop_view&& __other)
78-
: __cached_begin_() // Intentionally not propagating the cached begin iterator.
79-
, __count_(_VSTD::move(__other.__count_))
80-
, __base_(_VSTD::move(__other.__base_))
81-
{ }
82-
83-
_LIBCPP_HIDE_FROM_ABI
84-
constexpr drop_view& operator=(drop_view const& __other) {
85-
if constexpr (_UseCache) {
86-
__cached_begin_.reset();
87-
}
88-
__base_ = __other.__base_;
89-
__count_ = __other.__count_;
90-
return *this;
91-
}
92-
93-
_LIBCPP_HIDE_FROM_ABI
94-
constexpr drop_view& operator=(drop_view&& __other) {
95-
if constexpr (_UseCache) {
96-
__cached_begin_.reset();
97-
__other.__cached_begin_.reset();
98-
}
99-
__base_ = _VSTD::move(__other.__base_);
100-
__count_ = _VSTD::move(__other.__count_);
101-
return *this;
102-
}
103-
10465
_LIBCPP_HIDE_FROM_ABI constexpr _View base() const& requires copy_constructible<_View> { return __base_; }
10566
_LIBCPP_HIDE_FROM_ABI constexpr _View base() && { return _VSTD::move(__base_); }
10667

@@ -110,12 +71,12 @@ namespace ranges {
11071
random_access_range<const _View> && sized_range<const _View>))
11172
{
11273
if constexpr (_UseCache)
113-
if (__cached_begin_)
74+
if (__cached_begin_.__has_value())
11475
return *__cached_begin_;
11576

11677
auto __tmp = ranges::next(ranges::begin(__base_), __count_, ranges::end(__base_));
11778
if constexpr (_UseCache)
118-
__cached_begin_ = __tmp;
79+
__cached_begin_.__set(__tmp);
11980
return __tmp;
12081
}
12182

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
#ifndef _LIBCPP___RANGES_NON_PROPAGATING_CACHE_H
10+
#define _LIBCPP___RANGES_NON_PROPAGATING_CACHE_H
11+
12+
#include <__config>
13+
#include <__iterator/concepts.h> // indirectly_readable
14+
#include <__iterator/iterator_traits.h> // iter_reference_t
15+
#include <__memory/addressof.h>
16+
#include <concepts> // constructible_from
17+
#include <optional>
18+
#include <type_traits>
19+
20+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
21+
#pragma GCC system_header
22+
#endif
23+
24+
_LIBCPP_PUSH_MACROS
25+
#include <__undef_macros>
26+
27+
_LIBCPP_BEGIN_NAMESPACE_STD
28+
29+
// clang-format off
30+
31+
#if !defined(_LIBCPP_HAS_NO_RANGES)
32+
33+
namespace ranges {
34+
// __non_propagating_cache is a helper type that allows storing an optional value in it,
35+
// but which does not copy the source's value when it is copy constructed/assigned to,
36+
// and which resets the source's value when it is moved-from.
37+
//
38+
// This type is used as an implementation detail of some views that need to cache the
39+
// result of `begin()` in order to provide an amortized O(1) begin() method. Typically,
40+
// we don't want to propagate the value of the cache upon copy because the cached iterator
41+
// may refer to internal details of the source view.
42+
template<class _Tp>
43+
requires is_object_v<_Tp>
44+
class _LIBCPP_TEMPLATE_VIS __non_propagating_cache {
45+
optional<_Tp> __value_ = nullopt;
46+
47+
public:
48+
_LIBCPP_HIDE_FROM_ABI __non_propagating_cache() = default;
49+
50+
_LIBCPP_HIDE_FROM_ABI
51+
constexpr __non_propagating_cache(__non_propagating_cache const&) noexcept
52+
: __value_(nullopt)
53+
{ }
54+
55+
_LIBCPP_HIDE_FROM_ABI
56+
constexpr __non_propagating_cache(__non_propagating_cache&& __other) noexcept
57+
: __value_(nullopt)
58+
{
59+
__other.__value_.reset();
60+
}
61+
62+
_LIBCPP_HIDE_FROM_ABI
63+
constexpr __non_propagating_cache& operator=(__non_propagating_cache const& __other) noexcept {
64+
if (this != _VSTD::addressof(__other)) {
65+
__value_.reset();
66+
}
67+
return *this;
68+
}
69+
70+
_LIBCPP_HIDE_FROM_ABI
71+
constexpr __non_propagating_cache& operator=(__non_propagating_cache&& __other) noexcept {
72+
__value_.reset();
73+
__other.__value_.reset();
74+
return *this;
75+
}
76+
77+
_LIBCPP_HIDE_FROM_ABI
78+
constexpr _Tp& operator*() { return *__value_; }
79+
_LIBCPP_HIDE_FROM_ABI
80+
constexpr _Tp const& operator*() const { return *__value_; }
81+
82+
_LIBCPP_HIDE_FROM_ABI
83+
constexpr bool __has_value() const { return __value_.has_value(); }
84+
_LIBCPP_HIDE_FROM_ABI
85+
constexpr void __set(_Tp const& __value) { __value_.emplace(__value); }
86+
_LIBCPP_HIDE_FROM_ABI
87+
constexpr void __set(_Tp&& __value) { __value_.emplace(_VSTD::move(__value)); }
88+
};
89+
90+
struct __empty_cache { };
91+
} // namespace ranges
92+
93+
#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_RANGES)
94+
95+
_LIBCPP_END_NAMESPACE_STD
96+
97+
_LIBCPP_POP_MACROS
98+
99+
#endif // _LIBCPP___RANGES_NON_PROPAGATING_CACHE_H

libcxx/include/module.modulemap

+1
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ module std [system] {
613613
module empty_view { private header "__ranges/empty_view.h" }
614614
module enable_borrowed_range { private header "__ranges/enable_borrowed_range.h" }
615615
module enable_view { private header "__ranges/enable_view.h" }
616+
module non_propagating_cache { private header "__ranges/non_propagating_cache.h" }
616617
module ref_view { private header "__ranges/ref_view.h" }
617618
module size { private header "__ranges/size.h" }
618619
module subrange { private header "__ranges/subrange.h" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
// REQUIRES: modules-build
11+
12+
// WARNING: This test was generated by 'generate_private_header_tests.py'
13+
// and should not be edited manually.
14+
15+
// expected-error@*:* {{use of private header from outside its module: '__ranges/non_propagating_cache.h'}}
16+
#include <__ranges/non_propagating_cache.h>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
// UNSUPPORTED: libcpp-no-concepts
11+
// UNSUPPORTED: gcc-10
12+
13+
// __non_propagating_cache& operator=(__non_propagating_cache const&);
14+
15+
// ADDITIONAL_COMPILE_FLAGS: -Wno-self-assign
16+
17+
#include <ranges>
18+
19+
#include <cassert>
20+
#include <type_traits>
21+
#include <utility>
22+
23+
template<bool NoexceptCopy>
24+
struct CopyAssignable {
25+
int x;
26+
constexpr explicit CopyAssignable(int i) : x(i) { }
27+
CopyAssignable(CopyAssignable const&) = default;
28+
constexpr CopyAssignable& operator=(CopyAssignable const& other) noexcept(NoexceptCopy) {
29+
x = other.x;
30+
return *this;
31+
}
32+
constexpr bool operator==(CopyAssignable const& other) const { return x == other.x; }
33+
};
34+
35+
struct NotCopyAssignable {
36+
int x;
37+
constexpr explicit NotCopyAssignable(int i) : x(i) { }
38+
NotCopyAssignable(NotCopyAssignable const&) = default;
39+
NotCopyAssignable& operator=(NotCopyAssignable const&) = delete;
40+
constexpr bool operator==(NotCopyAssignable const& other) const { return x == other.x; }
41+
};
42+
43+
template <class T>
44+
constexpr void test() {
45+
using Cache = std::ranges::__non_propagating_cache<T>;
46+
static_assert(std::is_nothrow_copy_assignable_v<Cache>);
47+
48+
// Assign to an empty cache
49+
{
50+
Cache a; a.__set(T{3});
51+
Cache b;
52+
53+
Cache& result = (b = a);
54+
assert(&result == &b);
55+
assert(!b.__has_value()); // make sure we don't propagate
56+
57+
assert(a.__has_value()); // make sure we don't "steal" from the source
58+
assert(*a == T{3}); //
59+
}
60+
61+
// Assign to a non-empty cache
62+
{
63+
Cache a; a.__set(T{3});
64+
Cache b; b.__set(T{5});
65+
66+
Cache& result = (b = a);
67+
assert(&result == &b);
68+
assert(!b.__has_value()); // make sure we don't propagate
69+
70+
assert(a.__has_value()); // make sure we don't "steal" from the source
71+
assert(*a == T{3}); //
72+
}
73+
74+
// Self-assignment should not do anything (case with empty cache)
75+
{
76+
Cache b;
77+
Cache& result = (b = b);
78+
assert(&result == &b);
79+
assert(!b.__has_value());
80+
}
81+
82+
// Self-assignment should not do anything (case with non-empty cache)
83+
{
84+
Cache b; b.__set(T{5});
85+
Cache& result = (b = b);
86+
assert(&result == &b);
87+
assert(b.__has_value());
88+
assert(*b == T{5});
89+
}
90+
}
91+
92+
constexpr bool tests() {
93+
test<CopyAssignable<true>>();
94+
test<CopyAssignable<false>>();
95+
test<NotCopyAssignable>();
96+
test<int>();
97+
return true;
98+
}
99+
100+
int main(int, char**) {
101+
static_assert(tests());
102+
tests();
103+
return 0;
104+
}

0 commit comments

Comments
 (0)