diff --git a/libcxx/include/__ranges/drop_view.h b/libcxx/include/__ranges/drop_view.h --- a/libcxx/include/__ranges/drop_view.h +++ b/libcxx/include/__ranges/drop_view.h @@ -77,7 +77,7 @@ auto __tmp = ranges::next(ranges::begin(__base_), __count_, ranges::end(__base_)); if constexpr (_UseCache) - __cached_begin_.__set(__tmp); + __cached_begin_.__emplace(__tmp); return __tmp; } diff --git a/libcxx/include/__ranges/non_propagating_cache.h b/libcxx/include/__ranges/non_propagating_cache.h --- a/libcxx/include/__ranges/non_propagating_cache.h +++ b/libcxx/include/__ranges/non_propagating_cache.h @@ -13,6 +13,7 @@ #include <__iterator/concepts.h> // indirectly_readable #include <__iterator/iterator_traits.h> // iter_reference_t #include <__memory/addressof.h> +#include <__utility/forward.h> #include // constructible_from #include #include @@ -21,13 +22,8 @@ #pragma GCC system_header #endif -_LIBCPP_PUSH_MACROS -#include <__undef_macros> - _LIBCPP_BEGIN_NAMESPACE_STD -// clang-format off - #if !defined(_LIBCPP_HAS_NO_RANGES) namespace ranges { @@ -42,7 +38,19 @@ template requires is_object_v<_Tp> class _LIBCPP_TEMPLATE_VIS __non_propagating_cache { - optional<_Tp> __value_ = nullopt; + struct __deref_tag { }; + + // This helper class is needed to perform copy and move elision when + // constructing the contained type from an iterator. + struct __wrapper { + template + constexpr explicit __wrapper(_Args&& ...__args) : __t_(_VSTD::forward<_Args>(__args)...) { } + template + constexpr explicit __wrapper(__deref_tag, _Iter const& __iter) : __t_(*__iter) { } + _Tp __t_; + }; + + optional<__wrapper> __value_ = nullopt; public: _LIBCPP_HIDE_FROM_ABI __non_propagating_cache() = default; @@ -75,16 +83,24 @@ } _LIBCPP_HIDE_FROM_ABI - constexpr _Tp& operator*() { return *__value_; } + constexpr _Tp& operator*() { return __value_->__t_; } _LIBCPP_HIDE_FROM_ABI - constexpr _Tp const& operator*() const { return *__value_; } + constexpr _Tp const& operator*() const { return __value_->__t_; } _LIBCPP_HIDE_FROM_ABI constexpr bool __has_value() const { return __value_.has_value(); } + + template _LIBCPP_HIDE_FROM_ABI - constexpr void __set(_Tp const& __value) { __value_.emplace(__value); } + constexpr _Tp& __emplace_deref(_Iter const& __iter) { + return __value_.emplace(__deref_tag{}, __iter).__t_; + } + + template _LIBCPP_HIDE_FROM_ABI - constexpr void __set(_Tp&& __value) { __value_.emplace(_VSTD::move(__value)); } + constexpr _Tp& __emplace(_Args&& ...__args) { + return __value_.emplace(_VSTD::forward<_Args>(__args)...).__t_; + } }; struct __empty_cache { }; @@ -94,6 +110,4 @@ _LIBCPP_END_NAMESPACE_STD -_LIBCPP_POP_MACROS - #endif // _LIBCPP___RANGES_NON_PROPAGATING_CACHE_H diff --git a/libcxx/include/__ranges/reverse_view.h b/libcxx/include/__ranges/reverse_view.h --- a/libcxx/include/__ranges/reverse_view.h +++ b/libcxx/include/__ranges/reverse_view.h @@ -64,7 +64,7 @@ auto __tmp = _VSTD::make_reverse_iterator(ranges::next(ranges::begin(__base_), ranges::end(__base_))); if constexpr (_UseCache) - __cached_begin_.__set(__tmp); + __cached_begin_.__emplace(__tmp); return __tmp; } diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.copy.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.copy.pass.cpp --- a/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.copy.pass.cpp +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.copy.pass.cpp @@ -48,7 +48,7 @@ // Assign to an empty cache { - Cache a; a.__set(T{3}); + Cache a; a.__emplace(3); Cache b; Cache& result = (b = a); @@ -61,8 +61,8 @@ // Assign to a non-empty cache { - Cache a; a.__set(T{3}); - Cache b; b.__set(T{5}); + Cache a; a.__emplace(3); + Cache b; b.__emplace(5); Cache& result = (b = a); assert(&result == &b); @@ -82,7 +82,7 @@ // Self-assignment should not do anything (case with non-empty cache) { - Cache b; b.__set(T{5}); + Cache b; b.__emplace(5); Cache& result = (b = b); assert(&result == &b); assert(b.__has_value()); diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.move.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.move.pass.cpp --- a/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.move.pass.cpp +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/assign.move.pass.cpp @@ -47,7 +47,7 @@ // Assign to an empty cache { - Cache a; a.__set(T{3}); + Cache a; a.__emplace(3); Cache b; Cache& result = (b = std::move(a)); @@ -58,8 +58,8 @@ // Assign to a non-empty cache { - Cache a; a.__set(T{3}); - Cache b; b.__set(T{5}); + Cache a; a.__emplace(3); + Cache b; b.__emplace(5); Cache& result = (b = std::move(a)); assert(&result == &b); @@ -78,7 +78,7 @@ // Self-assignment should clear the cache (case with non-empty cache) { - Cache b; b.__set(T{5}); + Cache b; b.__emplace(5); Cache& result = (b = std::move(b)); assert(&result == &b); diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.copy.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.copy.pass.cpp --- a/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.copy.pass.cpp +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.copy.pass.cpp @@ -40,7 +40,7 @@ using Cache = std::ranges::__non_propagating_cache; static_assert(std::is_nothrow_copy_constructible_v); Cache a; - a.__set(T{3}); + a.__emplace(3); // Test with direct initialization { diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.move.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.move.pass.cpp --- a/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.move.pass.cpp +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/ctor.move.pass.cpp @@ -35,7 +35,7 @@ // Test with direct initialization { Cache a; - a.__set(T{3}); + a.__emplace(3); Cache b(std::move(a)); assert(!b.__has_value()); // make sure we don't propagate @@ -45,7 +45,7 @@ // Test with copy initialization { Cache a; - a.__set(T{3}); + a.__emplace(3); Cache b = std::move(a); assert(!b.__has_value()); // make sure we don't propagate diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/deref.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/deref.pass.cpp --- a/libcxx/test/libcxx/ranges/range.nonprop.cache/deref.pass.cpp +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/deref.pass.cpp @@ -24,14 +24,14 @@ // non-const version { - Cache cache; cache.__set(T{3}); + Cache cache; cache.__emplace(3); T& result = *cache; assert(result == T{3}); } // const version { - Cache cache; cache.__set(T{3}); + Cache cache; cache.__emplace(3); T const& result = *static_cast(cache); assert(result == T{3}); } diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/emplace.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/emplace.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/emplace.pass.cpp @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// template +// constexpr T& __emplace(Args&& ...); + +#include + +#include +#include + +template +struct X { + int value = -1; + template + constexpr friend bool operator==(X const& a, X const& b) { return I == J && a.value == b.value; } +}; + +constexpr bool test() { + { + using T = std::tuple<>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace(); + assert(&result == &*cache); + assert(result == T()); + } + + { + using T = std::tuple>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace(); + assert(&result == &*cache); + assert(result == T()); + } + { + using T = std::tuple>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace(X<0>{0}); + assert(&result == &*cache); + assert(result == T(X<0>{0})); + } + + { + using T = std::tuple, X<1>>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace(); + assert(&result == &*cache); + assert(result == T()); + } + { + using T = std::tuple, X<1>>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace(X<0>{0}, X<1>{1}); + assert(&result == &*cache); + assert(result == T(X<0>{0}, X<1>{1})); + } + + return true; +} + +int main(int, char**) { + static_assert(test()); + test(); + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/emplace_deref.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/emplace_deref.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/emplace_deref.pass.cpp @@ -0,0 +1,91 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// template +// constexpr T& __emplace_deref(Iter const&); + +#include + +#include +#include + +template +struct X { + int value = -1; + template + constexpr friend bool operator==(X const& a, X const& b) { return I == J && a.value == b.value; } +}; + +template +struct iterator_to { + T t; + constexpr T operator*() const { return t; } +}; + +struct NonMovable { + int value; + constexpr explicit NonMovable(int v) : value(v) { } + NonMovable(NonMovable&&) = delete; +}; + +// Generates the prvalue on the fly, without requiring it to be +// movable (because of C++20's copy and move elision rules). +template +struct on_the_fly_iterator_to { + int value; + constexpr T operator*() const { return T(value); } +}; + +constexpr bool test() { + { + using T = std::tuple<>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace_deref(iterator_to{}); + assert(&result == &*cache); + assert(result == T()); + } + { + using T = std::tuple>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace_deref(iterator_to{T(X<0>{0})}); + assert(&result == &*cache); + assert(result == T(X<0>{0})); + } + { + using T = std::tuple, X<1>>; + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + T& result = cache.__emplace_deref(iterator_to{T(X<0>{0}, X<1>{1})}); + assert(&result == &*cache); + assert(result == T(X<0>{0}, X<1>{1})); + } + + // Make sure that we do not require the type to be movable when we emplace-deref it. + // Copy elision should be performed instead, see http://eel.is/c++draft/range.nonprop.cache#note-1. + { + using Cache = std::ranges::__non_propagating_cache; + Cache cache; + NonMovable& result = cache.__emplace_deref(on_the_fly_iterator_to{3}); + assert(&result == &*cache); + assert(result.value == 3); + } + + return true; +} + +int main(int, char**) { + static_assert(test()); + test(); + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.nonprop.cache/has_value.pass.cpp b/libcxx/test/libcxx/ranges/range.nonprop.cache/has_value.pass.cpp --- a/libcxx/test/libcxx/ranges/range.nonprop.cache/has_value.pass.cpp +++ b/libcxx/test/libcxx/ranges/range.nonprop.cache/has_value.pass.cpp @@ -29,7 +29,7 @@ // __has_value on a non-empty cache { - Cache cache; cache.__set(T{}); + Cache cache; cache.__emplace(T{}); assert(cache.__has_value()); } }