/// \file // Range v3 library // // Copyright Casey Carter 2017 // // Use, modification and distribution is subject to the // Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // Project home: https://github.com/ericniebler/range-v3 // #ifndef RANGES_V3_UTILITY_OPTIONAL_HPP #define RANGES_V3_UTILITY_OPTIONAL_HPP #include <exception> #include <initializer_list> #include <memory> #include <new> #include <concepts/concepts.hpp> #include <range/v3/detail/config.hpp> #include <range/v3/utility/addressof.hpp> #include <range/v3/utility/in_place.hpp> #include <range/v3/utility/static_const.hpp> #include <range/v3/utility/swap.hpp> namespace ranges { template<typename> struct optional; struct bad_optional_access : std::exception { virtual const char * what() const noexcept override { return "bad optional access"; } }; struct nullopt_t { struct tag {}; explicit constexpr nullopt_t(tag) noexcept {} }; #if RANGES_CXX_INLINE_VARIABLES >= RANGES_CXX_INLINE_VARIABLES_17 inline constexpr nullopt_t nullopt{nullopt_t::tag{}}; #else /// \cond namespace detail { template<typename> struct nullopt_holder { static constexpr nullopt_t nullopt{nullopt_t::tag{}}; }; template<typename T> constexpr nullopt_t nullopt_holder<T>::nullopt; } // namespace detail /// \endcond namespace { constexpr auto & nullopt = detail::nullopt_holder<void>::nullopt; } #endif /// \cond namespace detail { template<typename = void> [[noreturn]] bool throw_bad_optional_access() { throw bad_optional_access{}; } namespace optional_adl { template<typename T, bool = std::is_trivially_destructible<T>::value> struct optional_storage { union { char dummy_; meta::_t<std::remove_cv<T>> data_; }; bool engaged_; constexpr optional_storage() noexcept : optional_storage( tag{}, meta::strict_and<detail::is_trivially_default_constructible<T>, detail::is_trivially_copyable<T>>{}) {} CPP_template(typename... Args)( // requires constructible_from<T, Args...>) // constexpr explicit optional_storage(in_place_t, Args &&... args) // noexcept(std::is_nothrow_constructible<T, Args...>::value) : data_(static_cast<Args &&>(args)...) , engaged_{true} {} constexpr void reset() noexcept { engaged_ = false; } private: struct tag {}; constexpr optional_storage(tag, std::false_type) noexcept : dummy_{} , engaged_{false} {} constexpr optional_storage(tag, std::true_type) noexcept : data_{} , engaged_{false} {} }; template<typename T> struct optional_storage<T, false> { union { char dummy_; meta::_t<std::remove_cv<T>> data_; }; bool engaged_; ~optional_storage() { reset(); } constexpr optional_storage() noexcept : dummy_{} , engaged_{false} {} CPP_template(typename... Args)( // requires constructible_from<T, Args...>) // constexpr explicit optional_storage(in_place_t, Args &&... args) // noexcept(std::is_nothrow_constructible<T, Args...>::value) : data_(static_cast<Args &&>(args)...) , engaged_{true} {} optional_storage(optional_storage const &) = default; optional_storage(optional_storage &&) = default; optional_storage & operator=(optional_storage const &) = default; optional_storage & operator=(optional_storage &&) = default; void reset() noexcept { if(engaged_) { data_.~T(); engaged_ = false; } } }; template<typename T> struct optional_base : private optional_storage<T> { using optional_storage<T>::optional_storage; using optional_storage<T>::reset; constexpr bool has_value() const noexcept { return engaged_; } constexpr T & operator*() & noexcept { return RANGES_EXPECT(engaged_), data_; } constexpr T const & operator*() const & noexcept { return RANGES_EXPECT(engaged_), data_; } constexpr T && operator*() && noexcept { return RANGES_EXPECT(engaged_), detail::move(data_); } constexpr T const && operator*() const && noexcept { return RANGES_EXPECT(engaged_), detail::move(data_); } constexpr T * operator->() noexcept { return RANGES_EXPECT(engaged_), detail::addressof(data_); } constexpr T const * operator->() const noexcept { return RANGES_EXPECT(engaged_), detail::addressof(data_); } CPP_member constexpr auto swap(optional_base & that) noexcept( std::is_nothrow_move_constructible<T>::value && is_nothrow_swappable<T>::value) -> CPP_ret(void)( // requires move_constructible<T> && swappable<T>) { constexpr bool can_swap_trivially = !::concepts::adl_swap_detail::is_adl_swappable_v<T> && detail::is_trivially_move_constructible<T>::value && detail::is_trivially_move_assignable<T>::value; swap_(meta::bool_<can_swap_trivially>{}, that); } protected: template<typename... Args> auto construct_from(Args &&... args) noexcept( std::is_nothrow_constructible<T, Args...>::value) -> CPP_ret(T &)( // requires constructible_from<T, Args...>) { RANGES_EXPECT(!engaged_); auto const address = static_cast<void *>(std::addressof(data_)); ::new(address) T(static_cast<Args &&>(args)...); engaged_ = true; return data_; } template<typename U> constexpr void assign_from(U && that) noexcept( std::is_nothrow_constructible<T, decltype(*static_cast<U &&>(that))>:: value && std::is_nothrow_assignable< T &, decltype(*static_cast<U &&>(that))>::value) { if(!that.has_value()) reset(); else if(engaged_) data_ = *static_cast<U &&>(that); else { auto const address = static_cast<void *>(detail::addressof(data_)); ::new(address) T(*static_cast<U &&>(that)); engaged_ = true; } } private: constexpr void swap_(std::true_type, optional_base & that) noexcept { ranges::swap(static_cast<optional_storage<T> &>(*this), static_cast<optional_storage<T> &>(that)); } constexpr void swap_(std::false_type, optional_base & that) noexcept( std::is_nothrow_move_constructible<T>::value && is_nothrow_swappable<T>::value) { if(that.engaged_ == engaged_) { if(engaged_) ranges::swap(data_, that.data_); } else { auto & src = engaged_ ? *this : that; auto & dst = engaged_ ? that : *this; dst.construct_from(detail::move(src.data_)); src.reset(); } } using optional_storage<T>::engaged_; using optional_storage<T>::data_; }; template<typename T> struct optional_base<T &> { optional_base() = default; template<typename Arg> constexpr explicit CPP_ctor(optional_base)(in_place_t, Arg && arg)( // noexcept(true) // requires constructible_from<T &, Arg>) : ptr_(detail::addressof(arg)) {} constexpr bool has_value() const noexcept { return ptr_; } constexpr T & operator*() const noexcept { return RANGES_EXPECT(ptr_), *ptr_; } constexpr T * operator->() const noexcept { return RANGES_EXPECT(ptr_), ptr_; } constexpr void reset() noexcept { ptr_ = nullptr; } CPP_member constexpr auto swap(optional_base & that) noexcept( is_nothrow_swappable<T>::value) -> CPP_ret(void)( // requires swappable<T>) { if(ptr_ && that.ptr_) ranges::swap(*ptr_, *that.ptr_); else ranges::swap(ptr_, that.ptr_); } protected: template<typename U> constexpr auto construct_from(U && ref) noexcept -> CPP_ret(T &)( // requires convertible_to<U &, T &>) { RANGES_EXPECT(!ptr_); ptr_ = detail::addressof(ref); return *ptr_; } template<typename U> constexpr void assign_from(U && that) { if(ptr_ && that.ptr_) *ptr_ = *that.ptr_; else ptr_ = that.ptr_; } private: T * ptr_ = nullptr; }; template<typename T> struct optional_copy : optional_base<T> { optional_copy() = default; optional_copy(optional_copy const & that) noexcept( std::is_nothrow_copy_constructible<T>::value) { if(that.has_value()) this->construct_from(*that); } optional_copy(optional_copy &&) = default; optional_copy & operator=(optional_copy const &) = default; optional_copy & operator=(optional_copy &&) = default; using optional_base<T>::optional_base; }; template<typename T> using copy_construct_layer = meta::if_c<std::is_copy_constructible<T>::value && !detail::is_trivially_copy_constructible<T>::value, optional_copy<T>, optional_base<T>>; template<typename T> struct optional_move : copy_construct_layer<T> { optional_move() = default; optional_move(optional_move const &) = default; optional_move(optional_move && that) noexcept( std::is_nothrow_move_constructible<T>::value) { if(that.has_value()) this->construct_from(std::move(*that)); } optional_move & operator=(optional_move const &) = default; optional_move & operator=(optional_move &&) = default; using copy_construct_layer<T>::copy_construct_layer; }; template<typename T> using move_construct_layer = meta::if_c<std::is_move_constructible<T>::value && !detail::is_trivially_move_constructible<T>::value, optional_move<T>, copy_construct_layer<T>>; template<typename T> struct optional_copy_assign : move_construct_layer<T> { optional_copy_assign() = default; optional_copy_assign(optional_copy_assign const &) = default; optional_copy_assign(optional_copy_assign &&) = default; optional_copy_assign & operator=(optional_copy_assign const & that) // noexcept(std::is_nothrow_copy_constructible<T>::value && std::is_nothrow_copy_assignable<T>::value) { this->assign_from(that); return *this; } optional_copy_assign & operator=(optional_copy_assign &&) = default; using move_construct_layer<T>::move_construct_layer; }; template<typename T> struct deleted_copy_assign : move_construct_layer<T> { deleted_copy_assign() = default; deleted_copy_assign(deleted_copy_assign const &) = default; deleted_copy_assign(deleted_copy_assign &&) = default; deleted_copy_assign & operator=(deleted_copy_assign const &) = delete; deleted_copy_assign & operator=(deleted_copy_assign &&) = default; using move_construct_layer<T>::move_construct_layer; }; template<typename T> using copy_assign_layer = meta::if_c< std::is_copy_constructible<T>::value && std::is_copy_assignable<T>::value, meta::if_c<std::is_reference<T>::value || !(detail::is_trivially_copy_constructible<T>::value && detail::is_trivially_copy_assignable<T>::value), optional_copy_assign<T>, move_construct_layer<T>>, deleted_copy_assign<T>>; template<typename T> struct optional_move_assign : copy_assign_layer<T> { optional_move_assign() = default; optional_move_assign(optional_move_assign const &) = default; optional_move_assign(optional_move_assign &&) = default; optional_move_assign & operator=(optional_move_assign const &) = default; optional_move_assign & operator=(optional_move_assign && that) noexcept( std::is_nothrow_move_constructible<T>::value && std::is_nothrow_move_assignable<T>::value) { this->assign_from(std::move(that)); return *this; } using copy_assign_layer<T>::copy_assign_layer; }; template<typename T> struct deleted_move_assign : copy_assign_layer<T> { deleted_move_assign() = default; deleted_move_assign(deleted_move_assign const &) = default; deleted_move_assign(deleted_move_assign &&) = default; deleted_move_assign & operator=(deleted_move_assign const &) = default; deleted_move_assign & operator=(deleted_move_assign &&) = delete; using copy_assign_layer<T>::copy_assign_layer; }; template<typename T> using move_assign_layer = meta::if_c< std::is_move_constructible<T>::value && std::is_move_assignable<T>::value, meta::if_c<std::is_reference<T>::value || !(detail::is_trivially_move_constructible<T>::value && detail::is_trivially_move_assignable<T>::value), optional_move_assign<T>, copy_assign_layer<T>>, deleted_move_assign<T>>; } // namespace optional_adl } // namespace detail /// \endcond // clang-format off CPP_def ( template(typename U, typename T) concept optional_should_convert, requires(int)(void()) && !( constructible_from<T, optional<U> & > || constructible_from<T, optional<U> && > || constructible_from<T, optional<U> const & > || constructible_from<T, optional<U> const &&> || convertible_to<optional<U> &, T> || convertible_to<optional<U> &&, T> || convertible_to<optional<U> const &, T> || convertible_to<optional<U> const &&, T> ) ); CPP_def ( template(typename U, typename T) concept optional_should_convert_assign, optional_should_convert<U, T> && !(assignable_from<T &, optional<U> &> || assignable_from<T &, optional<U> &&> || assignable_from<T &, optional<U> const &> || assignable_from<T &, optional<U> const &&>) ); // clang-format on template<typename T> struct optional : detail::optional_adl::move_assign_layer<T> { private: using base_t = detail::optional_adl::move_assign_layer<T>; public: CPP_assert(destructible<T>); static_assert(std::is_object<T>::value || std::is_lvalue_reference<T>::value, ""); static_assert((bool)!same_as<nullopt_t, uncvref_t<T>>, ""); static_assert((bool)!same_as<in_place_t, uncvref_t<T>>, ""); using value_type = meta::_t<std::remove_cv<T>>; constexpr optional() noexcept {} constexpr optional(nullopt_t) noexcept : optional{} {} optional(optional const &) = default; optional(optional &&) = default; using base_t::base_t; CPP_template(typename E, typename... Args)( // requires constructible_from<T, std::initializer_list<E> &, Args...>) // constexpr explicit optional(in_place_t, std::initializer_list<E> il, Args &&... args) // noexcept(std::is_nothrow_constructible<T, std::initializer_list<E> &, Args...>::value) : base_t(in_place, il, static_cast<Args &&>(args)...) {} template<typename U = T> constexpr CPP_ctor(optional)(U && v)( // requires(!defer::same_as<detail::decay_t<U>, in_place_t>) && (!defer::same_as<detail::decay_t<U>, optional>)&&defer::constructible_from<T, U> && defer::convertible_to<U, T>) : base_t(in_place, static_cast<U &&>(v)) {} template<typename U = T> explicit constexpr CPP_ctor(optional)(U && v)( // requires(!defer::same_as<detail::decay_t<U>, in_place_t>) && (!defer::same_as<detail::decay_t<U>, optional>)&&defer::constructible_from<T, U> && (!defer::convertible_to<U, T>)) : base_t(in_place, static_cast<U &&>(v)) {} template<typename U> CPP_ctor(optional)(optional<U> const & that)( // requires optional_should_convert<U, T> && constructible_from<T, U const &> && convertible_to<U const &, T>) { if(that.has_value()) base_t::construct_from(*that); } template<typename U> explicit CPP_ctor(optional)(optional<U> const & that)( // requires optional_should_convert<U, T> && constructible_from<T, U const &> && (!convertible_to<U const &, T>)) { if(that.has_value()) base_t::construct_from(*that); } template<typename U> CPP_ctor(optional)(optional<U> && that)( // requires optional_should_convert<U, T> && constructible_from<T, U> && convertible_to<U, T>) { if(that.has_value()) base_t::construct_from(detail::move(*that)); } template<typename U> explicit CPP_ctor(optional)(optional<U> && that)( // requires optional_should_convert<U, T> && constructible_from<T, U> && (!convertible_to<U, T>)) { if(that.has_value()) base_t::construct_from(detail::move(*that)); } constexpr optional & operator=(nullopt_t) noexcept { reset(); return *this; } optional & operator=(optional const &) = default; optional & operator=(optional &&) = default; template<typename U = T> constexpr auto operator=(U && u) noexcept( std::is_nothrow_constructible<T, U>::value && std::is_nothrow_assignable<T &, U>::value) -> CPP_ret(optional &)( // requires(!defer::same_as<optional, detail::decay_t<U>>) && (!(defer::satisfies<T, std::is_scalar> && defer::same_as<T, detail::decay_t<U>>)) && defer::constructible_from<T, U> && defer::assignable_from<T &, U>) { if(has_value()) **this = static_cast<U &&>(u); else base_t::construct_from(static_cast<U &&>(u)); return *this; } template<typename U> constexpr auto operator=(optional<U> const & that) -> CPP_ret(optional &)( // requires optional_should_convert_assign<U, T> && constructible_from<T, const U &> && assignable_from<T &, const U &>) { base_t::assign_from(that); return *this; } template<typename U> constexpr auto operator=(optional<U> && that) -> CPP_ret(optional &)( // requires optional_should_convert_assign<U, T> && constructible_from<T, U> && assignable_from<T &, U>) { base_t::assign_from(std::move(that)); return *this; } template<typename... Args> auto emplace(Args &&... args) noexcept( std::is_nothrow_constructible<T, Args...>::value) -> CPP_ret(T &)( // requires constructible_from<T, Args...>) { reset(); return base_t::construct_from(static_cast<Args &&>(args)...); } template<typename E, typename... Args> auto emplace(std::initializer_list<E> il, Args &&... args) noexcept( std::is_nothrow_constructible<T, std::initializer_list<E> &, Args...>::value) -> CPP_ret(T &)( // requires constructible_from<T, std::initializer_list<E> &, Args &&...>) { reset(); return base_t::construct_from(il, static_cast<Args &&>(args)...); } using base_t::swap; using base_t::operator->; using base_t::operator*; constexpr explicit operator bool() const noexcept { return has_value(); } using base_t::has_value; constexpr T const & value() const & { return (has_value() || detail::throw_bad_optional_access()), **this; } constexpr T & value() & { return (has_value() || detail::throw_bad_optional_access()), **this; } constexpr T const && value() const && { return (has_value() || detail::throw_bad_optional_access()), detail::move(**this); } constexpr T && value() && { return (has_value() || detail::throw_bad_optional_access()), detail::move(**this); } CPP_template(typename U)( // requires copy_constructible<T> && convertible_to<U, T>) // constexpr T value_or(U && u) const & { return has_value() ? **this : static_cast<T>((U &&) u); } CPP_template(typename U)( // requires move_constructible<T> && convertible_to<U, T>) // constexpr T value_or(U && u) && { return has_value() ? detail::move(**this) : static_cast<T>((U &&) u); } using base_t::reset; }; /// \cond namespace detail { namespace optional_adl { constexpr bool convert_bool(bool b) noexcept { return b; } // Relational operators [optional.relops] template<typename T, typename U> constexpr auto operator==(optional<T> const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(*x == *y))) -> decltype(convert_bool(*x == *y)) { return x.has_value() == y.has_value() && (!x || convert_bool(*x == *y)); } template<typename T, typename U> constexpr auto operator!=(optional<T> const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(*x != *y))) -> decltype(convert_bool(*x != *y)) { return x.has_value() != y.has_value() || (x && convert_bool(*x != *y)); } template<typename T, typename U> constexpr auto operator<(optional<T> const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(*x < *y))) -> decltype(convert_bool(*x < *y)) { return y && (!x || convert_bool(*x < *y)); } template<typename T, typename U> constexpr auto operator>(optional<T> const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(*x > *y))) -> decltype(convert_bool(*x > *y)) { return x && (!y || convert_bool(*x > *y)); } template<typename T, typename U> constexpr auto operator<=(optional<T> const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(*x <= *y))) -> decltype(convert_bool(*x <= *y)) { return !x || (y && convert_bool(*x <= *y)); } template<typename T, typename U> constexpr auto operator>=(optional<T> const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(*x >= *y))) -> decltype(convert_bool(*x >= *y)) { return !y || (x && convert_bool(*x >= *y)); } // Comparisons with nullopt [optional.nullops] template<typename T> constexpr bool operator==(optional<T> const & x, nullopt_t) noexcept { return !x; } template<typename T> constexpr bool operator==(nullopt_t, optional<T> const & x) noexcept { return !x; } template<typename T> constexpr bool operator!=(optional<T> const & x, nullopt_t) noexcept { return !!x; } template<typename T> constexpr bool operator!=(nullopt_t, optional<T> const & x) noexcept { return !!x; } template<typename T> constexpr bool operator<(optional<T> const &, nullopt_t) noexcept { return false; } template<typename T> constexpr bool operator<(nullopt_t, optional<T> const & x) noexcept { return !!x; } template<typename T> constexpr bool operator>(optional<T> const & x, nullopt_t) noexcept { return !!x; } template<typename T> constexpr bool operator>(nullopt_t, optional<T> const &) noexcept { return false; } template<typename T> constexpr bool operator<=(optional<T> const & x, nullopt_t) noexcept { return !x; } template<typename T> constexpr bool operator<=(nullopt_t, optional<T> const &) noexcept { return true; } template<typename T> constexpr bool operator>=(optional<T> const &, nullopt_t) noexcept { return true; } template<typename T> constexpr bool operator>=(nullopt_t, optional<T> const & x) noexcept { return !x; } // Comparisons with T [optional.comp_with_t] template<typename T, typename U> constexpr auto operator==(optional<T> const & x, U const & y) // noexcept(noexcept(convert_bool(*x == y))) // -> decltype(convert_bool(*x == y)) { return x && convert_bool(*x == y); } template<typename T, typename U> constexpr auto operator==(T const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(x == *y))) // -> decltype(convert_bool(x == *y)) { return y && convert_bool(x == *y); } template<typename T, typename U> constexpr auto operator!=(optional<T> const & x, U const & y) // noexcept(noexcept(convert_bool(*x != y))) // -> decltype(convert_bool(*x != y)) { return !x || convert_bool(*x != y); } template<typename T, typename U> constexpr auto operator!=(T const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(x != *y))) // -> decltype(convert_bool(x != *y)) { return !y || convert_bool(x != *y); } template<typename T, typename U> constexpr auto operator<(optional<T> const & x, U const & y) // noexcept(noexcept(convert_bool(*x < y))) // -> decltype(convert_bool(*x < y)) { return !x || convert_bool(*x < y); } template<typename T, typename U> constexpr auto operator<(T const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(x < *y))) // -> decltype(convert_bool(x < *y)) { return y && convert_bool(x < *y); } template<typename T, typename U> constexpr auto operator>(optional<T> const & x, U const & y) // noexcept(noexcept(convert_bool(*x > y))) -> decltype(convert_bool(*x > y)) { return x && convert_bool(*x > y); } template<typename T, typename U> constexpr auto operator>(T const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(x > *y))) // -> decltype(convert_bool(x > *y)) { return !y || convert_bool(x > *y); } template<typename T, typename U> constexpr auto operator<=(optional<T> const & x, U const & y) // noexcept(noexcept(convert_bool(*x <= y))) // -> decltype(convert_bool(*x <= y)) { return !x || convert_bool(*x <= y); } template<typename T, typename U> constexpr auto operator<=(T const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(x <= *y))) // -> decltype(convert_bool(x <= *y)) { return y && convert_bool(x <= *y); } template<typename T, typename U> constexpr auto operator>=(optional<T> const & x, U const & y) // noexcept(noexcept(convert_bool(*x >= y))) // -> decltype(convert_bool(*x >= y)) { return x && convert_bool(*x >= y); } template<typename T, typename U> constexpr auto operator>=(T const & x, optional<U> const & y) // noexcept(noexcept(convert_bool(x >= *y))) // -> decltype(convert_bool(x >= *y)) { return !y || convert_bool(x >= *y); } // clang-format off template<typename T> auto CPP_auto_fun(swap)(optional<T> &x, optional<T> &y) ( return x.swap(y) ) // clang-format on } // namespace optional_adl } // namespace detail /// \endcond // clang-format off template<typename T> constexpr auto CPP_auto_fun(make_optional)(T &&t) ( return optional<detail::decay_t<T>>{static_cast<T &&>(t)} ) template<typename T, typename... Args> constexpr auto CPP_auto_fun(make_optional)(Args &&... args) ( return optional<T>{in_place, static_cast<Args &&>(args)...} ) template<typename T, typename U, typename... Args> constexpr auto CPP_auto_fun(make_optional)(std::initializer_list<U> il, Args &&... args) ( return optional<T>{in_place, il, static_cast<Args &&>(args)...} ) // clang-format on /// \cond namespace detail { template<typename T, typename Tag = void, bool Enable = true> struct non_propagating_cache : optional<T> { non_propagating_cache() = default; constexpr non_propagating_cache(nullopt_t) noexcept {} constexpr non_propagating_cache(non_propagating_cache const &) noexcept : optional<T>{} {} constexpr non_propagating_cache(non_propagating_cache && that) noexcept : optional<T>{} { that.optional<T>::reset(); } constexpr non_propagating_cache & operator=( non_propagating_cache const &) noexcept { optional<T>::reset(); return *this; } constexpr non_propagating_cache & operator=( non_propagating_cache && that) noexcept { that.optional<T>::reset(); optional<T>::reset(); return *this; } using optional<T>::operator=; }; template<typename T, typename Tag> struct non_propagating_cache<T, Tag, false> {}; } // namespace detail /// \endcond } // namespace ranges #endif