diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/postlude.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/postlude.hpp index 03ad2d8a7b..1c1b915f5a 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/postlude.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/postlude.hpp @@ -95,3 +95,5 @@ static_assert(false, "BSONCXX_ENUM must be undef'ed"); #pragma pop_macro("BSONCXX_IF_GCC") #pragma pop_macro("BSONCXX_IF_CLANG") #pragma pop_macro("BSONCXX_IF_GNU_LIKE") + +#pragma pop_macro("BSONCXX_FWD") diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/prelude.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/prelude.hpp index 6a43ff4101..2f154c437f 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/prelude.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/prelude.hpp @@ -45,6 +45,8 @@ #undef _bsoncxxDisableWarningImpl_for_MSVC #pragma push_macro("_bsoncxxDisableWarningImpl_for_GNU") #undef _bsoncxxDisableWarningImpl_for_GNU +#pragma push_macro("BSONCXX_FWD") +#undef BSONCXX_FWD // compiler.hpp #pragma push_macro("BSONCXX_INLINE") diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/util.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/util.hpp index b42ce4682b..045c8c80a9 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/util.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/config/util.hpp @@ -131,3 +131,6 @@ #define _bsoncxxDisableWarningImpl_for_MSVC(...) \ BSONCXX_IF_MSVC(BSONCXX_PRAGMA(warning(disable : __VA_ARGS__))) + +#define BSONCXX_FWD(...) static_cast(__VA_ARGS__) + diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/operators.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/operators.hpp index 8e889b0b1a..f9e553361f 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/operators.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/operators.hpp @@ -114,6 +114,11 @@ class strong_ordering { DEFOP(<=); DEFOP(>=); #pragma pop_macro("DEFOP") + + // nonstd: Swap greater/less values + constexpr strong_ordering inverted() const noexcept { + return *this < 0 ? greater : *this > 0 ? less : *this; + } }; #pragma push_macro("INLINE_VAR") @@ -151,8 +156,8 @@ struct compare_three_way { typename R, typename = decltype(tag_invoke( std::declval(), std::declval(), std::declval()))> - constexpr strong_ordering impl(L const& l, R const& r, rank<2>) const { - return tag_invoke(*this, l, r); + constexpr static strong_ordering impl(L const& l, R const& r, rank<2>) { + return tag_invoke(compare_three_way{}, l, r); } template @@ -165,12 +170,20 @@ struct compare_three_way { * implementation of tag_invoke(compare_three_way, l, r) */ struct ordering_operators { + template + constexpr static auto impl(const L& l, const R& r, rank<1>) + BSONCXX_RETURNS(tag_invoke(compare_three_way{}, l, r)); + + template + constexpr static auto impl(const L& l, const R& r, rank<0>) + BSONCXX_RETURNS(tag_invoke(compare_three_way{}, r, l).inverted()); + #pragma push_macro("DEFOP") #undef DEFOP #define DEFOP(Oper) \ template \ constexpr friend auto operator Oper(const L& l, const R& r) \ - BSONCXX_RETURNS(tag_invoke(compare_three_way{}, l, r) Oper 0) + BSONCXX_RETURNS(ordering_operators::impl(l, r, rank<1>{}) Oper 0) DEFOP(<); DEFOP(>); DEFOP(<=); @@ -178,6 +191,35 @@ struct ordering_operators { #pragma pop_macro("DEFOP") }; +template +std::false_type is_partially_ordered_with_f(rank<0>); + +template +auto is_partially_ordered_with_f(rank<1>, + const T& l = soft_declval(), + const U& r = soft_declval()) // + -> true_t r), + decltype(l < r), + decltype(l >= r), + decltype(l <= r), + decltype(r < l), + decltype(r > l), + decltype(r <= l), + decltype(r >= l)>; + +template +struct is_partially_ordered_with : decltype(is_partially_ordered_with_f(rank<1>{})) {}; + +template +struct is_totally_ordered + : conjunction, is_partially_ordered_with> {}; + +template +struct is_totally_ordered_with : conjunction, + is_totally_ordered, + is_equality_comparable, + is_partially_ordered_with> {}; + } // namespace detail } // namespace bsoncxx diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/optional.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/optional.hpp index ab574c8998..eb9db49474 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/optional.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/optional.hpp @@ -14,72 +14,770 @@ #pragma once -#include +#include +#include +#include +#include +#include +#include +#include +#include -#if defined(BSONCXX_POLY_USE_MNMLSTC) +#include "./operators.hpp" +#include "./type_traits.hpp" -#include +#include namespace bsoncxx { + namespace v_noabi { + namespace stdx { -using ::core::make_optional; -using ::core::nullopt; -using ::core::nullopt_t; -using ::core::optional; +/** + * @brief Implementation of an std::optional-like class template + * + * Presents mostly the same interface as std::optional from C++17. + * + * @tparam T The type being made "optional" + */ +template +class optional; -} // namespace stdx -} // namespace v_noabi -} // namespace bsoncxx +/** + * @brief Exception type thrown upon attempted access to a value-less optional + * via a throwing accessor API. + */ +class bad_optional_access : public std::exception { + public: + const char* what() const noexcept override { + return "bad_optional_access()"; + } +}; +/// Tag type to represent an empty optional value +struct nullopt_t { + explicit constexpr nullopt_t(std::nullptr_t) noexcept {} +}; +/// Tag constant to construct or compare with an empty optional value +static constexpr nullopt_t nullopt{0}; +/// Tag used to call the emplacement-constructor of optional +static constexpr struct in_place_t { +} in_place; -#elif defined(BSONCXX_POLY_USE_BOOST) +namespace detail { -#include -#include -#include +// Terminates the program when an illegal use of optional is attempted +[[noreturn]] inline void terminate_disengaged_optional(const char* what) noexcept { + (void)std::fprintf(stderr, "%s: Invalid attempted use of disengaged optional\n", what); + std::terminate(); +} +// Throws bad_optional_access for throwing optional member functions +[[noreturn]] inline void throw_bad_optional() { + throw bad_optional_access(); +} +// Base class of std::optional. Implementation detail, defined later +template +struct optional_base_class; -namespace bsoncxx { -namespace v_noabi { -namespace stdx { +// Base case: Things are not optionals. +template +std::true_type not_an_optional_f(const T&); +// More-specialized if given an optional or any class derived from a template +// specialization thereof. +template +std::false_type not_an_optional_f(const optional&); -using ::boost::optional; -using nullopt_t = ::boost::none_t; +// Utility trait to detect specializations of stdx::optional +template +struct not_an_optional : decltype(not_an_optional_f(std::declval())) {}; -// TODO(MSVC): This would be better expressed as constexpr, but VS2015U1 can't do it. -const nullopt_t nullopt{::boost::none}; -using ::boost::make_optional; +template +struct enable_opt_conversion + : bsoncxx::detail::conjunction< // + std::is_constructible, + bsoncxx::detail::disjunction< // + std::is_same, + bsoncxx::detail::negation< + bsoncxx::detail::conjunction&>, + std::is_constructible const&>, + std::is_constructible&&>, + std::is_constructible const&&>, + std::is_convertible&, T>, + std::is_convertible const&, T>, + std::is_convertible&&, T>, + std::is_convertible const&&, T>>>>> {}; -} // namespace stdx -} // namespace v_noabi -} // namespace bsoncxx +template +struct enable_opt_value_conversion // + : bsoncxx::detail::conjunction< // + std::is_constructible, + bsoncxx::detail::negation>, + bsoncxx::detail::negation>>, + bsoncxx::detail::disjunction< + bsoncxx::detail::negation>, // + detail::not_an_optional>>> {}; -#elif defined(BSONCXX_POLY_USE_STD) +} // namespace detail -#include +template +class optional : bsoncxx::detail::equality_operators, + bsoncxx::detail::ordering_operators, + public detail::optional_base_class::type { + public: + /// The type of value held within this optional + using value_type = T; + /// An lvalue-reference-to-mutable T + using reference = bsoncxx::detail::add_lvalue_reference_t; + /// An lvalue-reference-to-const T + using const_reference = + bsoncxx::detail::add_lvalue_reference_t>; + /// An rvalue-reference-to-mutable T + using rvalue_reference = bsoncxx::detail::add_rvalue_reference_t; + /// An rvalue-reference-to-const T + using const_rvalue_reference = + bsoncxx::detail::add_rvalue_reference_t>; + /// A pointer-to-mutable T + using pointer = bsoncxx::detail::add_pointer_t; + /// A pointer-to-const T + using const_pointer = bsoncxx::detail::add_pointer_t; -namespace bsoncxx { -namespace v_noabi { -namespace stdx { + // Constructors [1] + optional() = default; + constexpr optional(nullopt_t) noexcept {} + + // Ctor [2] and [3] are provided by base classes + optional(const optional&) = default; + optional(optional&&) = default; + // Same with assignments + optional& operator=(const optional&) = default; + optional& operator=(optional&&) = default; + ~optional() = default; + + // In-place constructors + template + bsoncxx_cxx14_constexpr explicit optional(in_place_t, Args&&... args) noexcept( + noexcept(T(BSONCXX_FWD(args)...))) { + this->emplace(BSONCXX_FWD(args)...); + } + + template + bsoncxx_cxx14_constexpr explicit optional( + in_place_t, + std::initializer_list il, + Args&&... args) noexcept(noexcept(T(il, BSONCXX_FWD(args)...))) { + this->emplace(il, BSONCXX_FWD(args)...); + } + + // Explicit converting constructor. Only available if implicit conversion is + // not possible. + template < + typename U = T, + bsoncxx::detail::requires_t, + bsoncxx::detail::negation>> = 0> + bsoncxx_cxx14_constexpr explicit optional(U&& arg) noexcept( + std::is_nothrow_constructible::value) + : optional(in_place, BSONCXX_FWD(arg)) {} + + // Implicit converting constructor. Only available if implicit conversion is + // possible. + template , + std::is_convertible> = 0> + bsoncxx_cxx14_constexpr optional(U&& arg) noexcept(std::is_nothrow_constructible::value) + : optional(in_place, BSONCXX_FWD(arg)) {} + + template , + bsoncxx::detail::negation>> = 0> + bsoncxx_cxx14_constexpr explicit optional(optional const& other) noexcept( + std::is_nothrow_constructible>::value) { + if (other.has_value()) { + this->emplace(*other); + } + } + + template , + std::is_convertible> = 0> + bsoncxx_cxx14_constexpr optional(optional const& other) noexcept( + std::is_nothrow_constructible>::value) { + if (other.has_value()) { + this->emplace(*other); + } + } + + template < + typename U, + bsoncxx::detail::requires_t, + bsoncxx::detail::negation>> = 0> + bsoncxx_cxx14_constexpr explicit optional(optional&& other) noexcept( + std::is_nothrow_constructible>::value) { + if (other.has_value()) { + this->emplace(*BSONCXX_FWD(other)); + } + } + + template , + std::is_convertible> = 0> + bsoncxx_cxx14_constexpr optional(optional&& other) noexcept( + std::is_nothrow_constructible>::value) { + if (other.has_value()) { + this->emplace(*BSONCXX_FWD(other)); + } + } + + constexpr bool has_value() const noexcept { + return this->_has_value; + } + constexpr explicit operator bool() const noexcept { + return this->has_value(); + } + + // Unchecked dereference operators + bsoncxx_cxx14_constexpr reference operator*() & noexcept { + _assert_has_value("operator*() &"); + return this->_storage.value; + } + bsoncxx_cxx14_constexpr const_reference operator*() const& noexcept { + _assert_has_value("operator*() const&"); + return this->_storage.value; + } + bsoncxx_cxx14_constexpr rvalue_reference operator*() && noexcept { + _assert_has_value("operator*() &&"); + return static_cast(**this); + } + bsoncxx_cxx14_constexpr const_rvalue_reference operator*() const&& noexcept { + _assert_has_value("operator*() const&&"); + return static_cast(**this); + } + + // (Unchecked) member-access operators + bsoncxx_cxx14_constexpr pointer operator->() noexcept { + _assert_has_value("operator->()"); + return std::addressof(**this); + } + bsoncxx_cxx14_constexpr const_pointer operator->() const noexcept { + _assert_has_value("operator->() const"); + return std::addressof(**this); + } + + // Checked accessors + bsoncxx_cxx14_constexpr reference value() & { + _throw_if_empty(); + return **this; + } + bsoncxx_cxx14_constexpr const_reference value() const& { + _throw_if_empty(); + return **this; + } + bsoncxx_cxx14_constexpr rvalue_reference value() && { + _throw_if_empty(); + return static_cast(**this); + } + bsoncxx_cxx14_constexpr const_rvalue_reference value() const&& { + _throw_if_empty(); + return static_cast(**this); + } + + // Checked value-or-alternative + template + bsoncxx_cxx14_constexpr value_type value_or(U&& dflt) const& { + if (has_value()) { + return **this; + } else { + return static_cast(BSONCXX_FWD(dflt)); + } + } + + template + bsoncxx_cxx14_constexpr value_type value_or(U&& dflt) && { + if (has_value()) { + return *std::move(*this); + } else { + return static_cast(BSONCXX_FWD(dflt)); + } + } + + private: + bsoncxx_cxx14_constexpr void _assert_has_value(const char* msg) const noexcept { + if (!this->has_value()) { + detail::terminate_disengaged_optional(msg); + } + } + + bsoncxx_cxx14_constexpr void _throw_if_empty() const { + if (!this->has_value()) { + detail::throw_bad_optional(); + } + } +}; + +/** + * @brief Construct an optional by decay-copying the given value into a new + * optional> + * + * @param value The value being made into an optional + */ +template +bsoncxx_cxx14_constexpr optional> make_optional(T&& value) noexcept( + std::is_nothrow_constructible, T&&>::value) { + return optional>(BSONCXX_FWD(value)); +} + +/** + * @brief Emplace-construct a new optional of the given type with the given + * constructor arguments + * + * @tparam T The type to be constructed + * @param args Constructor arguments + */ +template +bsoncxx_cxx14_constexpr optional make_optional(Args&&... args) noexcept( + std::is_nothrow_constructible::value) { + return optional(in_place, BSONCXX_FWD(args)...); +} + +/** + * @brief Emplace-construct a new optional of the given type with the given + * arguments (accepts an init-list as the first argument) + */ +template +bsoncxx_cxx14_constexpr optional +make_optional(std::initializer_list il, Args&&... args) noexcept( + std::is_nothrow_constructible, Args&&...>::value) { + return optional(in_place, il, BSONCXX_FWD(args)...); +} + +namespace detail { + +/** + * @brief Union template that defines the storage for an optional's data. + */ +template ::value> +union storage_for { + // Placeholder member for disengaged optional + char nothing; + // Member that holds the actual value + T value; + + // Default-construct activates the placeholder + storage_for() noexcept : nothing(0) {} + + // Empty special members allow the union to be used in semiregular contexts, + // but it is the responsibility of the using class to implement them properly + ~storage_for() {} + storage_for(const storage_for&) = delete; + storage_for& operator=(const storage_for&) = delete; +}; + +template +union storage_for { + char nothing; + T value; + storage_for() noexcept : nothing(0) {} + storage_for(const storage_for&) = delete; + storage_for& operator=(const storage_for&) = delete; +}; + +// Whether a type is copyable, moveable, or immobile +enum copymove_classification { + copyable, + movable, + immobile, +}; + +/// Classify the constructibility of the given type +template ::value, + bool CanMove = std::is_move_constructible::value> +constexpr copymove_classification classify_construct() { + return CanCopy ? copyable : CanMove ? movable : immobile; +} + +/// Classify the assignability of the given type +template ::value, + bool CanMove = std::is_move_assignable::value> +constexpr copymove_classification classify_assignment() { + return CanCopy ? copyable : CanMove ? movable : immobile; +} + +/** + * @brief Common base class for optional storage implementation + * + * @tparam T + */ +template +class optional_common_base; -using ::std::make_optional; -using ::std::nullopt; -using ::std::nullopt_t; -using ::std::optional; +/// Define the special member constructors for optional +template ()> +struct optional_construct_base; + +/// Define the special member assignment operators for optional +template ()> +struct optional_assign_base; + +template +struct optional_destruct_helper; + +template +using optional_destruct_base = + typename optional_destruct_helper::value>::template base; + +template +struct optional_assign_base : optional_construct_base {}; + +template +struct optional_assign_base : optional_construct_base { + // Constructors defer to base + optional_assign_base() = default; + optional_assign_base(optional_assign_base const&) = default; + optional_assign_base(optional_assign_base&&) = default; + ~optional_assign_base() = default; + + // No copy + bsoncxx_cxx14_constexpr optional_assign_base& operator=(const optional_assign_base&) = delete; + // Allow move-assign: + bsoncxx_cxx14_constexpr optional_assign_base& operator=(optional_assign_base&& other) = default; +}; + +template +struct optional_assign_base : optional_construct_base { + optional_assign_base() = default; + optional_assign_base(optional_assign_base const&) = default; + optional_assign_base(optional_assign_base&&) = default; + ~optional_assign_base() = default; + + // No assignment at all + optional_assign_base& operator=(const optional_assign_base&) = delete; + optional_assign_base& operator=(optional_assign_base&&) = delete; +}; + +template +struct optional_construct_base : optional_destruct_base {}; + +template +struct optional_construct_base : optional_destruct_base { + optional_construct_base() = default; + + optional_construct_base(const optional_construct_base&) = delete; + optional_construct_base(optional_construct_base&& other) = default; + optional_construct_base& operator=(const optional_construct_base&) = default; + optional_construct_base& operator=(optional_construct_base&&) = default; +}; + +template +struct optional_construct_base : optional_destruct_base { + optional_construct_base() = default; + optional_construct_base(const optional_construct_base&) = delete; + optional_construct_base& operator=(const optional_construct_base&) = default; + optional_construct_base& operator=(optional_construct_base&&) = default; +}; + +template <> +struct optional_destruct_helper { + template + struct base : optional_common_base { + // Special members defer to base + base() = default; + base(base const&) = default; + base(base&&) = default; + base& operator=(const base&) = default; + base& operator=(base&&) = default; + ~base() { + // Here we destroy the contained object during destruction. + this->reset(); + } + }; +}; + +template <> +struct optional_destruct_helper { + // Just fall-through to the common base, which has no special destructor + template + using base = optional_common_base; +}; + +// Optional's ADL-only operators live here: +struct optional_operators_base { + template + friend bsoncxx_cxx14_constexpr auto tag_invoke(bsoncxx::detail::equal_to, + optional const& left, + optional const& right) noexcept + -> bsoncxx::detail::requires_t> { + if (left.has_value() != right.has_value()) { + return false; + } + return !left.has_value() || *left == *right; + } + + template + friend constexpr auto tag_invoke(bsoncxx::detail::equal_to, + optional const& left, + U const& right) noexcept -> bsoncxx::detail:: + requires_t, bsoncxx::detail::is_equality_comparable> { + return left.has_value() && *left == right; + } + + template + friend constexpr bool tag_invoke(bsoncxx::detail::equal_to, + optional const& opt, + nullopt_t) noexcept { + return !opt.has_value(); + } + + template + bsoncxx_cxx14_constexpr friend auto tag_invoke(bsoncxx::detail::compare_three_way compare, + optional const& left, + optional const& right) + -> bsoncxx::detail::requires_t> { + if (left.has_value()) { + if (right.has_value()) { + return compare(*left, *right); + } else { + // non-null is greater than any null + return bsoncxx::detail::strong_ordering::greater; + } + } else { + if (right.has_value()) { + // Null is less than any non-null + return bsoncxx::detail::strong_ordering::less; + } else { + // Both are null + return bsoncxx::detail::strong_ordering::equal; + } + } + } + + template + bsoncxx_cxx14_constexpr friend auto tag_invoke(bsoncxx::detail::compare_three_way compare, + optional const& left, + U const& right) + -> bsoncxx::detail::requires_t, + bsoncxx::detail::is_totally_ordered_with> { + if (left.has_value()) { + return compare(*left, right); + } + // null optional is less-than any non-null value + return bsoncxx::detail::strong_ordering::less; + } + + template + constexpr friend bsoncxx::detail::strong_ordering tag_invoke( + bsoncxx::detail::compare_three_way compare, optional const& left, nullopt_t) { + return compare(left.has_value(), false); + } +}; + +// An ADL-visible swap() should only be available for swappable objects +template ::value> +struct optional_swap_mixin {}; + +template +struct optional_swap_mixin { + bsoncxx_cxx14_constexpr friend void swap(optional& left, optional& right) noexcept( + std::is_nothrow_move_constructible::value&& + bsoncxx::detail::is_nothrow_swappable::value) { + left.swap(right); + } +}; + +// Common base class of all optionals +template +class optional_common_base : optional_operators_base, optional_swap_mixin { + using storage_type = detail::storage_for>; + + public: + optional_common_base() = default; + ~optional_common_base() = default; + + optional_common_base(const optional_common_base& other) noexcept( + std::is_nothrow_copy_constructible::value) { + if (other._has_value) { + this->emplace(other._storage.value); + } + } + + optional_common_base(optional_common_base&& other) noexcept( + std::is_nothrow_move_constructible::value) { + if (other._has_value) { + this->_emplace_construct_anew(std::move(other)._storage.value); + } + } + + optional_common_base& operator=(const optional_common_base& other) noexcept( + std::is_nothrow_copy_assignable::value) { + this->_assign(BSONCXX_FWD(other)); + return *this; + } + + optional_common_base& operator=(optional_common_base&& other) noexcept( + std::is_nothrow_move_assignable::value) { + this->_assign(BSONCXX_FWD(other)); + return *this; + } + + /** + * @internal + * @brief If the optional is holding a value, destroy that value and set ourselves null + */ + void reset() noexcept { + if (this->_has_value) { + this->_storage.value.~T(); + } + this->_has_value = false; + } + + /** + * @internal + * @brief If the optional is holding a value, destroy that value. Construct + * a new value in-place using the given arguments. + */ + template + T& emplace(Args&&... args) { + this->reset(); + this->_emplace_construct_anew(BSONCXX_FWD(args)...); + return this->_storage.value; + } + + /** + * @internal + * @brief If the optional is holding a value, destroy that value. Construct + * a new value in-place using the given arguments. + */ + template + T& emplace(std::initializer_list il, Args&&... args) { + this->reset(); + this->_emplace_construct_anew(il, BSONCXX_FWD(args)...); + return this->_storage.value; + } + + /** + * @internal + * @brief Special swap for optional values that removes need for a temporary + */ + bsoncxx_cxx14_constexpr void swap(optional_common_base& other) noexcept( + std::is_nothrow_move_constructible::value&& + bsoncxx::detail::is_nothrow_swappable::value) { + if (other._has_value) { + if (this->_has_value) { + using std::swap; + // Defer to the underlying swap + swap(this->_storage.value, other._storage.value); + } else { + // "steal" the other's value + this->emplace(std::move(other._storage.value)); + other.reset(); + } + } else if (this->_has_value) { + other.emplace(std::move(this->_storage.value)); + this->reset(); + } else { + // Neither optional has a value, so do nothing + } + } + + private: + friend optional; + storage_type _storage; + bool _has_value = false; + + /** + * @internal + * @brief In-place construct a new value from the given arguments. Assumes + * that the optional does not have a live value. + */ + template + void _emplace_construct_anew(Args&&... args) noexcept( + std::is_nothrow_constructible::value) { + new (std::addressof(this->_storage.value)) T(BSONCXX_FWD(args)...); + this->_has_value = true; + } + + /** + * @internal + * @brief Perform the semantics of the assignment operator. + */ + template + void _assign(U&& other_storage) { + if (other_storage._has_value) { + // We are receiving a value + if (this->_has_value) { + // We already have a value. Invoke the underlying assignment. + this->_storage.value = BSONCXX_FWD(other_storage)._storage.value; + } else { + // We don't have a value. Use the constructor. + this->_emplace_construct_anew(BSONCXX_FWD(other_storage)._storage.value); + } + } else { + // We are receiving nullopt. Destroy our value, if present: + this->reset(); + } + } +}; + +template +struct optional_base_class { + using type = optional_assign_base; +}; + +template >>::value> +struct optional_hash; + +// Hash is "disabled" if the underlying type is not hashable (disabled = cannot construct the hash +// invocable) +template +struct optional_hash { + optional_hash() = delete; + optional_hash(const optional_hash&) = delete; +}; + +template +struct optional_hash { + using Td = bsoncxx::detail::remove_const_t; + constexpr std::size_t operator()(const optional& opt) const + noexcept(noexcept(std::hash()(std::declval()))) { + return opt.has_value() ? std::hash()(*opt) // + : std::hash()(nullptr); + } +}; + +} // namespace detail } // namespace stdx + } // namespace v_noabi + } // namespace bsoncxx -#else -#error "Cannot find a valid polyfill for optional" -#endif +namespace std { + +template +struct hash> + : bsoncxx::v_noabi::stdx::detail::optional_hash {}; + +} // namespace std #include namespace bsoncxx { namespace stdx { +using ::bsoncxx::v_noabi::stdx::in_place; +using ::bsoncxx::v_noabi::stdx::in_place_t; using ::bsoncxx::v_noabi::stdx::make_optional; using ::bsoncxx::v_noabi::stdx::nullopt; using ::bsoncxx::v_noabi::stdx::nullopt_t; diff --git a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/type_traits.hpp b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/type_traits.hpp index 8c1f7a9e7a..a075d25fbe 100644 --- a/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/type_traits.hpp +++ b/src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/type_traits.hpp @@ -15,6 +15,7 @@ #pragma once #include +#include #include @@ -316,7 +317,7 @@ using true_t = std::true_type; namespace impl_requires { template -R norm_conjunction(...); +R norm_conjunction(const R&); template conjunction norm_conjunction(const conjunction&); @@ -473,6 +474,47 @@ struct rank : rank {}; template <> struct rank<0> {}; +namespace swap_detection { + +using std::swap; + +///! Declare an unusable variadic swap. If not present, MSVC 19.00 (VS2015) errors in +///! this header and complains "'std::swap': function does not take 1 arguments" (???) +void swap(...) = delete; + +template +auto is_swappable_f(rank<0>) -> std::false_type; + +template +auto is_swappable_f(rank<1>) // + noexcept(noexcept(swap(std::declval(), std::declval())) // + && noexcept(swap(std::declval(), std::declval()))) + -> true_t(), std::declval())), + decltype(swap(std::declval(), std::declval()))>; + +template +auto is_nothrow_swappable_f(rank<0>) -> std::false_type; + +template +auto is_nothrow_swappable_f(rank<1>) // + -> bool_constant(), std::declval())) && + noexcept(swap(std::declval(), std::declval()))>; + +} // namespace swap_detection + +template +struct is_swappable_with : decltype(swap_detection::is_swappable_f(rank<1>{})) {}; + +template +struct is_nothrow_swappable_with + : decltype(swap_detection::is_nothrow_swappable_f(rank<1>{})) {}; + +template +struct is_swappable : is_swappable_with {}; + +template +struct is_nothrow_swappable : is_nothrow_swappable_with {}; + } // namespace detail } // namespace bsoncxx diff --git a/src/bsoncxx/test/CMakeLists.txt b/src/bsoncxx/test/CMakeLists.txt index 34d77778e5..b980f72567 100644 --- a/src/bsoncxx/test/CMakeLists.txt +++ b/src/bsoncxx/test/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable(test_bson bson_value.cpp json.cpp oid.cpp + optional.test.cpp view_or_value.cpp make_unique.test.cpp string_view.test.cpp @@ -93,7 +94,7 @@ if(ENABLE_MACRO_GUARD_TESTS) DEFINE_NO_DEPRECATED BSONCXX_NO_DEPRECATED BSONCXX_UNREACHABLE # prelude.hpp - _bsoncxxDisableWarningImpl_for_GCC + _bsoncxxDisableWarningImpl_for_GCC # util.hpp _bsoncxxDisableWarningImpl_for_GNU _bsoncxxDisableWarningImpl_for_MSVC _bsoncxxDisableWarningImpl_for_Clang @@ -104,6 +105,7 @@ if(ENABLE_MACRO_GUARD_TESTS) BSONCXX_CONCAT_IMPL bsoncxx_cxx14_constexpr BSONCXX_FORCE_SEMICOLON + BSONCXX_FWD BSONCXX_PRAGMA _bsoncxxPragma BSONCXX_STRINGIFY @@ -138,6 +140,7 @@ set_dist_list(src_bsoncxx_test_DIST catch.hh json.cpp oid.cpp + optional.test.cpp test_macro_guards.cpp.in to_string.hh view_or_value.cpp diff --git a/src/bsoncxx/test/catch.hh b/src/bsoncxx/test/catch.hh index 948b9b511d..8f5e3421cf 100644 --- a/src/bsoncxx/test/catch.hh +++ b/src/bsoncxx/test/catch.hh @@ -94,6 +94,20 @@ struct StringMaker> { return "{nullopt}"; } }; + +template <> +struct StringMaker { + static std::string convert(bsoncxx::detail::strong_ordering o) { + if (o < 0) { + return "[less-than]"; + } else if (o > 0) { + return "[greater-than]"; + } else { + return "[equal/equivalent]"; + } + } +}; + } // namespace Catch #include diff --git a/src/bsoncxx/test/optional.test.cpp b/src/bsoncxx/test/optional.test.cpp new file mode 100644 index 0000000000..bfa0c83cfa --- /dev/null +++ b/src/bsoncxx/test/optional.test.cpp @@ -0,0 +1,621 @@ +#include +// +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using bsoncxx::stdx::in_place; +using bsoncxx::stdx::nullopt; +using bsoncxx::stdx::optional; + +#if defined(_MSC_VER) && _MSC_VER < 1910 || defined(__apple_build_version__) +/// ! Prior to LWG 2543, uttering the name of an invalid std::hash is ill-formed. +/// ! This is fixed in C++11, but MSVC 2015 (19.00) and old AppleClang libc++ don't +/// !have the fix. As such, one cannot detect whether a type is hashable. +#define NO_LWG_2543 +#endif + +namespace { + +template +using deref_t = decltype(*std::declval()); + +template +using value_t = decltype(std::declval().value()); + +template +using arrow_t = decltype(std::declval().operator->()); + +#ifndef NO_LWG_2543 +template > +struct is_hashable + : bsoncxx::detail::conjunction>, + bsoncxx::detail::is_invocable, const T&>> {}; +#else + +template +struct is_hashable : std::false_type {}; +#endif + +struct not_default_constructible { + explicit not_default_constructible(int); +}; + +struct allows_moving_explicit_conversion { + // Requires an rvalue-reference to an unalike type + explicit allows_moving_explicit_conversion(int&&) {} +}; + +struct allows_moving_implicit_conversion { + // Requires an rvalue-reference to an unalike type + allows_moving_implicit_conversion(int&&) {} +}; + +struct immobile { + std::mutex mtx; +}; + +struct not_ordered {}; + +struct not_copyable { + not_copyable(not_copyable&&) = default; + not_copyable& operator=(not_copyable&&) = default; +}; + +template +bool assert_sameness() { + static_assert(Trait::value == Trait>::value, "Fail"); + return true; +} + +template +bool check_convert_alike() { + static_assert(std::is_convertible::value // + == std::is_convertible::value, + "fail"); + static_assert(std::is_convertible::value // + == std::is_convertible::value, + "fail"); + static_assert(std::is_convertible::value // + == std::is_convertible::value, + "fail"); + static_assert(std::is_convertible::value // + == std::is_convertible::value, + "fail"); + static_assert(std::is_convertible::value // + == std::is_convertible::value, + "fail"); + return true; +} + +template +bool check_construct_alike() { + static_assert(std::is_constructible::value // + == std::is_constructible::value, + "fail"); + static_assert(std::is_constructible::value // + == std::is_constructible::value, + "fail"); + static_assert(std::is_constructible::value // + == std::is_constructible::value, + "fail"); + static_assert(std::is_constructible::value // + == std::is_constructible::value, + "fail"); + static_assert(std::is_constructible::value // + == std::is_constructible::value, + "fail"); + return true; +} + +template +bool check_conversions() { + return check_convert_alike, optional>() && + check_convert_alike>() && + check_construct_alike, optional>() && + check_construct_alike>(); +} + +template +bool static_checks() { + assert_sameness(); + assert_sameness(); + assert_sameness(); + assert_sameness(); + assert_sameness(); + assert_sameness(); + assert_sameness(); + assert_sameness(); + static_assert(bsoncxx::detail::is_equality_comparable>::value == + bsoncxx::detail::is_equality_comparable::value, + "fail"); + static_assert(bsoncxx::detail::is_totally_ordered_with>::value == + bsoncxx::detail::is_totally_ordered>::value, + "fail"); + static_assert( + std::is_constructible, T>::value == std::is_constructible::value, "fail"); + static_assert(std::is_constructible, bsoncxx::stdx::nullopt_t>{}, "fail"); + // Assert we return proper reference types + static_assert(std::is_same>, T&&>{}, "fail"); + static_assert(std::is_same const>, const T&&>{}, "fail"); + static_assert(std::is_same const&>, const T&>{}, "fail"); + static_assert(std::is_same&>, T&>{}, "fail"); + // .value() + static_assert(std::is_same>, T&&>{}, "fail"); + static_assert(std::is_same const>, const T&&>{}, "fail"); + static_assert(std::is_same const&>, const T&>{}, "fail"); + static_assert(std::is_same&>, T&>{}, "fail"); + // operator-> + static_assert(std::is_same>, T*>{}, "fail"); + static_assert(std::is_same const>, const T*>{}, "fail"); + static_assert(std::is_same const&>, const T*>{}, "fail"); + static_assert(std::is_same&>, T*>{}, "fail"); + return check_conversions(); +} + +} // namespace + +static_assert(bsoncxx::detail::is_totally_ordered{}, "fail"); +static_assert(bsoncxx::detail::is_totally_ordered{}, "fail"); +static_assert(!bsoncxx::detail::is_totally_ordered{}, "fail"); + +#ifndef NO_LWG_2543 +static_assert(is_hashable>::value, "fail"); +static_assert(!is_hashable>::value, "fail"); +#endif + +// Having this static_assert appear prior to static_checks prevents a later static assert error +// that occurs only on MSVC 19.29 (VS2019). Obviously. +static_assert(bsoncxx::detail::is_totally_ordered>{}, "fail"); +// It's a useful check on its own, but now you are cursed with this knowledge just as I have been. +// pain. + +TEST_CASE("Trait checks") { + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks>()); + CHECK(static_checks>()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(static_checks()); + CHECK(check_conversions()); + CHECK(check_conversions()); + CHECK(check_conversions()); + CHECK(check_conversions()); + CHECK(check_conversions()); + CHECK(check_conversions()); + CHECK(check_conversions()); + CHECK(check_conversions()); + CHECK(check_conversions>()); + CHECK(check_conversions()); + CHECK(check_conversions()); +} + +TEST_CASE("optional constructors") { + // (1) + { + optional opt = optional(); + CHECK_FALSE(opt); + + optional opt2 = optional(nullopt); + CHECK_FALSE(opt2); + } + // (2) + { + optional opt1 = optional(); + optional opt2 = optional(opt1); + CHECK_FALSE(opt2); + } + + // (3) + { + optional opt1 = optional(123); + optional opt2 = optional(std::move(opt1)); + CHECK(*opt2 == 123); + } + + // (4) + { + struct Src { + int s; + }; + struct Dest { + // Can construct a Dest from Src. + Dest(Src s) { + this->d = s.s; + } + int d; + }; + + Src s; + s.s = 123; + optional opt_src = optional(s); + optional opt_dest = opt_src; + CHECK(opt_dest->d == 123); + } + + // (5) + { + struct Src { + int s; + }; + struct Dest { + // Can construct a Dest from Src. + Dest(Src s) { + this->d = s.s; + } + int d; + }; + + Src s; + s.s = 123; + optional opt_src = optional(s); + optional opt_dest = std::move(opt_src); + CHECK(opt_dest->d == 123); + } + + // (6) + { + struct Foo { + Foo(int a, int b) { + this->c = a + b; + } + int c; + }; + optional opt = optional(in_place, 1, 2); + CHECK(opt->c == 3); + } + + // (7) + { + struct Foo { + Foo(std::initializer_list) {} + }; + std::initializer_list il = {1, 2}; + optional opt = optional(in_place, il); + (void)opt; + } + + // (8) + { + struct Foo { + int f; + }; + Foo f{0}; + optional opt = optional(std::move(f)); + (void)opt; + } +} + +TEST_CASE("optional assignment operator") { + // (1) + { + optional foo; + optional& ref = (foo = nullopt); + CHECK(!foo); + CHECK(!ref); + } + + // (2) + { + optional foo; + optional other = 123; + optional& ref = (foo = other); + CHECK(foo); + CHECK(*foo == 123); + CHECK(ref); + CHECK(*ref == 123); + } + + // (3) + { + optional foo; + optional other = 123; + optional& ref = (foo = std::move(other)); + CHECK(foo); + CHECK(*foo == 123); + CHECK(ref); + CHECK(*ref == 123); + } + + // (4) + { + optional foo = 123; + CHECK(*foo == 123); + } + + // (5) + { + struct Src { + int s; + }; + struct Dest { + // Can construct a Dest from Src. + Dest(Src s) { + this->d = s.s; + } + int d; + }; + + Src s; + s.s = 123; + optional opt_src = optional(s); + optional opt_dest = opt_src; + CHECK(opt_dest->d == 123); + } + + // (6) + { + struct Src {}; + struct Dest { + // Can construct a Dest from Src. + Dest(Src) {} + }; + + Src s; + optional opt_src = optional(s); + optional opt_dest = std::move(opt_src); + (void)opt_dest; + } +} + +TEST_CASE("optional operator->") { + struct Foo { + int x; + }; + optional opt = Foo(); + opt->x = 123; + CHECK(opt->x == 123); +} + +TEST_CASE("optional operator bool") { + struct Foo { + int x; + }; + optional opt = Foo(); + CHECK(opt); + CHECK(opt.has_value()); + opt = nullopt; + CHECK(!opt); + CHECK(!opt.has_value()); +} + +TEST_CASE("optional value()") { + optional opt = 123; + CHECK(opt.value() == 123); +} + +TEST_CASE("optional value_or()") { + struct Src { + Src(int x) : x(x) {} + int x; + }; + struct Dest { + // Can construct a Dest from Src. + Dest(Src s) : x(s.x) {} + int x; + }; + + optional opt = nullopt; + Dest d = opt.value_or(Src(123)); + CHECK(d.x == 123); +} + +TEST_CASE("optional reset()") { + optional opt = 123; + opt.reset(); + CHECK(!opt); +} + +TEST_CASE("optional emplace()") { + optional opt = 123; + opt.emplace(456); + CHECK(*opt == 456); +} + +TEST_CASE("make_optional") { + auto opt = bsoncxx::stdx::make_optional(123); + CHECK(opt); + CHECK(*opt == 123); +} + +TEST_CASE("optional swap") { + optional opt1 = 123; + optional opt2 = nullopt; + CHECK(opt1.has_value()); + CHECK(!opt2.has_value()); + opt1.swap(opt2); + CHECK(!opt1.has_value()); + CHECK(opt2.has_value()); +} + +TEST_CASE("optional: Nontrivial contents") { + optional str = "abcd1234"; + CHECK(str == "abcd1234"); + { + auto dup = str; + CHECK(dup == str); + } + CHECK(str == "abcd1234"); + + optional> aptr; + CHECK(aptr != nullptr); + CHECK(aptr == bsoncxx::stdx::nullopt); + { + auto dup = std::move(aptr); + CHECK(aptr == dup); + } + aptr = bsoncxx::stdx::make_unique(31); + CHECK(aptr != nullopt); + REQUIRE(aptr != nullptr); + CHECK(**aptr == 31); + { + auto dup = std::move(aptr); + CHECK(aptr); + CHECK(aptr == nullptr); + REQUIRE(dup); + REQUIRE(dup != nullptr); + CHECK(**dup == 31); + } + CHECK(aptr == nullptr); +} + +TEST_CASE("Comparisons") { + optional a = 21; + optional b = 23; + optional c; + CHECK(a != nullopt); + CHECK(a == 21); + CHECK(b != nullopt); + CHECK(b == 23); + CHECK(c == nullopt); + // Null compares less-than values: + CHECK(c < 42); + CHECK(a < b); + CHECK(c < a); + CHECK(c < b); + CHECK(c == nullopt); + CHECK(nullopt < a); + CHECK(nullopt == c); + CHECK(a != b); + CHECK(c != a); + CHECK(c != b); + CHECK(c == c); + + optional dbl = 3.14; + CHECK(dbl != a); +} + +template +void check_ordered(T const& lesser, U const& greater, std::string desc) { + CAPTURE(__func__, desc); + CHECK(lesser < greater); + CHECK(greater > lesser); + CHECK(lesser <= greater); + CHECK(greater >= lesser); + CHECK_FALSE(greater < lesser); + CHECK_FALSE(lesser > greater); + CHECK(lesser != greater); + CHECK_FALSE(lesser == greater); +} + +template +void check_equivalent(T const& a, U const& b, std::string desc) { + CAPTURE(__func__, desc); + CHECK(a == b); + CHECK(b == a); + CHECK_FALSE(a != b); + CHECK_FALSE(b != a); + CHECK(a <= b); + CHECK(b >= a); + CHECK_FALSE(a < b); + CHECK_FALSE(b > a); + CHECK_FALSE(b < a); + CHECK_FALSE(a > b); +} + +template +void regular_cases(T low_value, U high_value) { + optional a, b; + check_equivalent(a, b, "Null optionals"); + check_equivalent(a, nullopt, "Compare with nullopt"); + a = low_value; + check_equivalent(a, a, "Self-compare"); + check_equivalent(a, low_value, "Engaged == value"); + check_ordered(a, high_value, "low enganged < high value"); + check_ordered(b, a, "Value > null optional"); + check_ordered(nullopt, a, "Value > nullopt"); + using std::swap; + swap(a, b); + check_equivalent(b, low_value, "low-value after swap"); + check_ordered(b, high_value, "High-value compare after swap"); + check_ordered(a, b, "Optional compare after swap"); + swap(a, b); + b.emplace(high_value); + check_ordered(a, b, "Engaged compare"); + swap(a, b); + check_ordered(b, a, "Engaged compare after swap"); + swap(a, b); + + a = b; + check_equivalent(a, b, "Equal after assignment"); + check_equivalent(a, high_value, "'a' received high value"); + check_ordered(low_value, a, "'a' is greater than high-value after assignment"); +} + +TEST_CASE("Optional: Cross-comparisons") { + regular_cases(2, 4); + regular_cases(2, 4.0); + regular_cases(std::string("abc"), std::string("xyz")); + regular_cases(std::string("abc"), "xyz"); + regular_cases(std::string("abc"), bsoncxx::stdx::string_view("xyz")); + regular_cases(bsoncxx::stdx::string_view("abc"), std::string("xyz")); +} + +template +std::size_t hashit(const T& what) { + return std::hash{}(what); +} + +TEST_CASE("Optional-of-const-T") { + optional a; + check_equivalent(a, nullopt, "Null of const"); + a.emplace(21); + check_equivalent(a, 21, "Const 21"); + CHECK(hashit(a) == hashit(21)); + auto b = a; + CHECK(a == b); +} + +TEST_CASE("Optional: Hashing") { + optional a, b; + CHECK(hashit(a) == hashit(a)); + CHECK(hashit(a) == hashit(b)); + b.emplace(41); + CHECK(hashit(41) == hashit(b)); + CHECK(hashit(a) != hashit(b)); // (Extremely probable, but not certain) + a.emplace(41); + CHECK(hashit(a) == hashit(b)); + optional c = b; + CHECK(hashit(c) == hashit(a)); +} + +struct in_place_convertible { + bool constructed_from_in_place = false; + in_place_convertible() = default; + in_place_convertible(bsoncxx::stdx::in_place_t) : constructed_from_in_place(true) {} +}; + +TEST_CASE("optional conversions") { + static_assert(!std::is_constructible, optional>{}, "fail"); + + optional s1(bsoncxx::stdx::in_place); + CHECK(s1 == ""); + + optional q(bsoncxx::stdx::in_place); + REQUIRE(q.has_value()); + CHECK_FALSE(q->constructed_from_in_place); + + optional c_str = "foo"; + optional string = c_str; + optional string2 = std::move(c_str); + CHECK(string == c_str); + CHECK(string2 == c_str); +} diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.cpp b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.cpp index 064b33bdde..46e2175b0c 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.cpp +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.cpp @@ -31,23 +31,7 @@ void doc_to_bson_t(const bsoncxx::v_noabi::document::view& doc, bson_t* bson) { } // namespace -#if !defined(BSONCXX_POLY_USE_STD) - -scoped_bson_t::scoped_bson_t(bsoncxx::v_noabi::document::view_or_value doc) - : _is_initialized{true}, _doc{std::move(doc)} { - doc_to_bson_t(*_doc, &_bson); -} - -void scoped_bson_t::init_from_static(bsoncxx::v_noabi::document::view_or_value doc) { - _is_initialized = true; - _doc = std::move(doc); - doc_to_bson_t(*_doc, &_bson); -} - -#endif - -scoped_bson_t::scoped_bson_t( - bsoncxx::v_noabi::stdx::optional doc) +scoped_bson_t::scoped_bson_t(bsoncxx::stdx::optional doc) : _is_initialized{doc} { if (doc) { _doc = std::move(doc); diff --git a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.hh b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.hh index e07ecd21a7..288c1e0d8e 100644 --- a/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.hh +++ b/src/mongocxx/lib/mongocxx/v_noabi/mongocxx/private/libbson.hh @@ -52,24 +52,6 @@ class MONGOCXX_TEST_API scoped_bson_t { // scoped_bson_t(); -// In C++17 mode, we don't need these overloads - they end up being ambiguous. The C++17 optional -// can deal with out it. -#if !defined(BSONCXX_POLY_USE_STD) - // - // Constructs a new scoped_bson_t from a document view_or_value. - // - // The internal bson_t is considered initialized. - // - explicit scoped_bson_t(bsoncxx::v_noabi::document::view_or_value doc); - - // - // Initializes a bson_t from the provided document. - // - // The internal bson_t is considered initialized. - // - void init_from_static(bsoncxx::v_noabi::document::view_or_value doc); -#endif - // // Constructs a new scoped_bson_t from an optional document view_or_value. //