diff --git a/README.md b/README.md index b04e508..c161acb 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,8 @@ If you are working from a checkout, the top-level CMake project also supports - Non-intrusive registration of ordinary C++ types - Explicit control over lifetime and stored representation - Array support for raw arrays and smart-pointer-backed arrays -- Explicit variant construction and variant storage resolution +- Explicit variant construction plus whole-variant or held-alternative + resolution - Constructor deduction with explicit factory overrides when needed - Interface bindings and multibindings - Indexed and annotated resolution diff --git a/docs/core-concepts.md b/docs/core-concepts.md index d2da2ee..ab14876 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -287,14 +287,22 @@ Dingo handles variants in two distinct places: - `construct, constructor_detection>()` constructs the variant by selecting `A` as the alternative to build - `register_type<..., storage>, factory<...>>()` lets the - container store and later resolve the whole variant value + container store the variant and later resolve either the whole variant or its + currently held alternative The current rules are narrow on purpose: - the selected alternative must appear exactly once in the variant type -- resolution is for the whole variant, not for `A` or `B` directly -- unique variant storage resolves as the whole variant value or rvalue -- shared and external variant storage resolve as references to the whole variant +- the whole variant remains resolvable +- uniquely occurring alternatives are also resolvable from variant storage +- if a requested alternative is published but the current instance holds a + different alternative, resolution fails as `type_not_convertible_exception` +- duplicate alternative types are rejected at compile time for direct resolution +- unique variant storage resolves the whole variant as a value or rvalue, and a + held alternative as a value or rvalue +- shared and external variant storage resolve the whole variant as values, + references, or pointers, and a held alternative as values, references, or + pointers @@ -324,22 +332,27 @@ construct_container.register_type, storage>(3.5f); construct_container .construct, constructor_detection>(); assert(std::holds_alternative(detected)); -assert(std::get(detected).value == 7); [[maybe_unused]] auto explicit_ctor = construct_container.construct, constructor>(); assert(std::holds_alternative(explicit_ctor)); -assert(std::get(explicit_ctor).value == 3.5f); container<> unique_container; unique_container.register_type, storage>(); unique_container.register_type, storage>, factory>>(); -// Resolve the whole variant value from variant storage. +// Resolve either the whole variant or its currently held alternative. [[maybe_unused]] auto value = unique_container.resolve>(); assert(std::holds_alternative(value)); -assert(std::get(value).value == 0); + +[[maybe_unused]] auto selected = unique_container.resolve(); + +try { + unique_container.resolve(); + assert(false); +} catch (const type_not_convertible_exception&) { +} std::variant existing(std::in_place_type, 9); container<> external_container; @@ -349,7 +362,9 @@ external_container.register_type, storage&>>( [[maybe_unused]] auto& ref = external_container.resolve&>(); assert(&ref == &existing); assert(std::holds_alternative(ref)); -assert(std::get(ref).value == 9); + +[[maybe_unused]] auto& held = external_container.resolve(); +assert(&held == &std::get(existing)); ``` diff --git a/docs/examples.md b/docs/examples.md index 0e9374e..501c068 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -35,7 +35,8 @@ the feature you care about and start there. - [examples/storage/array.cpp](../examples/storage/array.cpp): register and resolve raw arrays plus smart-array forms - [examples/container/variant.cpp](../examples/container/variant.cpp): construct - variants and store registrations whose storage is itself a variant + variants and resolve either the whole variant or its held alternative from + variant storage ## Interfaces, Collections, And Dispatch diff --git a/examples/container/variant.cpp b/examples/container/variant.cpp index c3882a3..a6fc641 100644 --- a/examples/container/variant.cpp +++ b/examples/container/variant.cpp @@ -38,13 +38,11 @@ int main() { construct_container.construct, constructor_detection>(); assert(std::holds_alternative(detected)); - assert(std::get(detected).value == 7); [[maybe_unused]] auto explicit_ctor = construct_container.construct, constructor>(); assert(std::holds_alternative(explicit_ctor)); - assert(std::get(explicit_ctor).value == 3.5f); container<> unique_container; unique_container.register_type, storage>(); @@ -52,11 +50,18 @@ int main() { scope, storage>, factory>>(); - // Resolve the whole variant value from variant storage. + // Resolve either the whole variant or its currently held alternative. [[maybe_unused]] auto value = unique_container.resolve>(); assert(std::holds_alternative(value)); - assert(std::get(value).value == 0); + + [[maybe_unused]] auto selected = unique_container.resolve(); + + try { + unique_container.resolve(); + assert(false); + } catch (const type_not_convertible_exception&) { + } std::variant existing(std::in_place_type, 9); container<> external_container; @@ -67,6 +72,8 @@ int main() { external_container.resolve&>(); assert(&ref == &existing); assert(std::holds_alternative(ref)); - assert(std::get(ref).value == 9); + + [[maybe_unused]] auto& held = external_container.resolve(); + assert(&held == &std::get(existing)); //// } diff --git a/include/dingo/container.h b/include/dingo/container.h index 769fbf2..588a5e6 100644 --- a/include/dingo/container.h +++ b/include/dingo/container.h @@ -526,6 +526,9 @@ class container : public allocator_base { void check_interface_requirements() { using normalized_type = normalized_type_t; using normalized_interface_type = normalized_type_t; + constexpr bool is_alternative_type_interface = + is_alternative_type_interface_compatible_v; static_assert(!std::is_reference_v); if constexpr (detail::is_array_like_type_v) { @@ -546,7 +549,9 @@ class container : public allocator_base { } } static_assert( - std::is_convertible_v); + std::is_convertible_v || + is_alternative_type_interface, + "registered type must be pointer-convertible to the interface"); if constexpr (!std::is_same_v) { static_assert(detail::storage_interface_requirements_v< diff --git a/include/dingo/memory/arena_allocator.h b/include/dingo/memory/arena_allocator.h index 68d002d..5c264bd 100644 --- a/include/dingo/memory/arena_allocator.h +++ b/include/dingo/memory/arena_allocator.h @@ -114,7 +114,8 @@ template< typename Allocator = std::allocator > class arena template< typename T, std::size_t N > arena(T(&buffer)[N], std::size_t block_size = N * sizeof(T)) : arena(reinterpret_cast(buffer), N * sizeof(T), block_size) { - static_assert(std::is_trivial_v); + static_assert(std::is_trivially_default_constructible_v && + std::is_trivially_copyable_v); } template< typename T > arena(T& buffer, std::size_t block_size = sizeof(T)) @@ -238,4 +239,3 @@ bool operator != (const arena_allocator& x, const arena_al } } - diff --git a/include/dingo/registration/type_registration.h b/include/dingo/registration/type_registration.h index d22d576..d0a60a6 100644 --- a/include/dingo/registration/type_registration.h +++ b/include/dingo/registration/type_registration.h @@ -91,6 +91,30 @@ struct deduced_interface_type { using type = ::dingo::interfaces>; }; +template +struct deduced_interface_type< + StorageType, ScopeType, + std::void_t>>::type>> { + using type = ::dingo::interfaces< + typename ::dingo::detail::alternative_type_interface_types< + std::remove_cv_t>>::type>; +}; + +template +struct deduced_interface_type< + StorageType, ScopeType, + std::enable_if_t && + type_traits>>::enabled && + type_traits>>::is_value_borrowable && + is_alternative_type_v>>>> { + using type = ::dingo::interfaces< + typename ::dingo::detail::alternative_type_interface_types< + std::remove_cv_t>>::type>; +}; + template struct deduced_interface_type< StorageType, ScopeType, diff --git a/include/dingo/resolution/instance_resolver.h b/include/dingo/resolution/instance_resolver.h index 417a9f9..a17d4cd 100644 --- a/include/dingo/resolution/instance_resolver.h +++ b/include/dingo/resolution/instance_resolver.h @@ -284,6 +284,10 @@ struct instance_resolver { return storage.resolve(context, container); } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4702) +#endif template void* resolve_address(Context& context, Container& container, @@ -303,6 +307,9 @@ struct instance_resolver { return ::dingo::get_address( context, std::forward(instance)); } +#ifdef _MSC_VER +#pragma warning(pop) +#endif private: template diff --git a/include/dingo/resolution/type_conversion.h b/include/dingo/resolution/type_conversion.h index 27b77e1..dc77e95 100644 --- a/include/dingo/resolution/type_conversion.h +++ b/include/dingo/resolution/type_conversion.h @@ -111,6 +111,84 @@ Target* resolve_handle_or_borrow_pointer(Factory& factory, Context& context, factory, context, requested_type, registered_type); } } + +template +Target extract_alternative_type_value(Sum&& sum, type_descriptor requested_type, + type_descriptor registered_type) { + using selected_type = std::remove_const_t; + using alternative_type = std::remove_cv_t>; + + if (auto* value = + alternative_type_traits::template get( + sum)) { + return Target(std::move(*value)); + } + + throw make_type_not_convertible_exception(requested_type, registered_type); +} + +template +Target& resolve_alternative_type_reference(Sum& sum, + type_descriptor requested_type, + type_descriptor registered_type) { + using selected_type = std::remove_const_t; + using alternative_type = std::remove_cv_t>; + + if (auto* value = + alternative_type_traits::template get( + sum)) { + return *value; + } + + throw make_type_not_convertible_exception(requested_type, registered_type); +} + +template +Target* resolve_alternative_type_pointer(Sum& sum, + type_descriptor requested_type, + type_descriptor registered_type) { + return std::addressof( + resolve_alternative_type_reference(sum, requested_type, + registered_type)); +} + +template +using borrowed_value_type_t = std::remove_cv_t::borrow(std::declval()))>>; + +template +struct is_borrowed_alternative_type_alternative : std::false_type {}; + +template +struct is_borrowed_alternative_type : std::false_type {}; + +template +struct is_borrowed_alternative_type< + Source, Target, + std::enable_if_t::enabled && + type_traits::is_value_borrowable && + is_alternative_type_v>>> + : std::bool_constant< + std::is_same_v, borrowed_value_type_t>> {}; + +template +struct is_borrowed_alternative_type_alternative< + Source, Target, + std::enable_if_t::enabled && + type_traits::is_value_borrowable && + is_alternative_type_v>>> + : std::bool_constant< + !std::is_same_v, borrowed_value_type_t> && + (alternative_type_count, + std::remove_cv_t>::value == 1)> {}; + +template +inline constexpr bool is_borrowed_alternative_type_v = + is_borrowed_alternative_type::value; + +template +inline constexpr bool is_borrowed_alternative_type_alternative_v = + is_borrowed_alternative_type_alternative::value; } // namespace detail // TODO: logic here should not depend on knowledge of storage types, @@ -130,7 +208,40 @@ struct type_conversion { template struct type_conversion>> { + std::enable_if_t && + !is_alternative_type_v>> { + template + static Target apply(Factory& factory, Context& context, type_descriptor, + type_descriptor) { + return detail::resolve_source(factory, context); + } +}; + +template +struct type_conversion< + unique, Target, Source, + std::enable_if_t< + is_alternative_type_v && + !std::is_pointer_v && + !std::is_same_v, std::remove_cv_t> && + (detail::alternative_type_count>::value == 1)>> { + template + static Target apply(Factory& factory, Context& context, + type_descriptor requested_type, + type_descriptor registered_type) { + auto&& source = detail::resolve_source(factory, context); + return detail::extract_alternative_type_value( + std::move(source), requested_type, registered_type); + } +}; + +template +struct type_conversion< + unique, Target, Source, + std::enable_if_t && + std::is_same_v, + std::remove_cv_t>>> { template static Target apply(Factory& factory, Context& context, type_descriptor, type_descriptor) { @@ -190,7 +301,72 @@ struct type_conversion, Source*, template struct type_conversion< StorageTag, Target, Source&, - std::enable_if_t::enabled && !std::is_pointer_v>> { + std::enable_if_t< + is_alternative_type_v && + !std::is_pointer_v && + !std::is_same_v, std::remove_cv_t> && + (detail::alternative_type_count>::value == 1)>> { + template + static Target& apply(Factory& factory, Context& context, + type_descriptor requested_type, + type_descriptor registered_type) { + return detail::resolve_alternative_type_reference( + detail::resolve_source(factory, context), requested_type, + registered_type); + } +}; + +template +struct type_conversion< + StorageTag, Target, Source&, + std::enable_if_t && + std::is_same_v, + std::remove_cv_t>>> { + template + static Target& apply(Factory& factory, Context& context, type_descriptor, + type_descriptor) { + return detail::resolve_source(factory, context); + } +}; + +template +struct type_conversion< + StorageTag, Target, Source*, + std::enable_if_t< + is_alternative_type_v && + !std::is_pointer_v && + !std::is_same_v, std::remove_cv_t> && + (detail::alternative_type_count>::value == 1)>> { + template + static Target& apply(Factory& factory, Context& context, + type_descriptor requested_type, + type_descriptor registered_type) { + return detail::resolve_alternative_type_reference( + *detail::resolve_source(factory, context), requested_type, + registered_type); + } +}; + +template +struct type_conversion< + StorageTag, Target, Source*, + std::enable_if_t && + std::is_same_v, + std::remove_cv_t>>> { + template + static Target& apply(Factory& factory, Context& context, type_descriptor, + type_descriptor) { + return *detail::resolve_source(factory, context); + } +}; + +template +struct type_conversion< + StorageTag, Target, Source&, + std::enable_if_t::enabled && !std::is_pointer_v && + !is_alternative_type_v>> { template static Target& apply(Factory& factory, Context& context, type_descriptor, type_descriptor) { @@ -198,6 +374,68 @@ struct type_conversion< } }; +template +struct type_conversion< + StorageTag, Target*, Source&, + std::enable_if_t< + is_alternative_type_v && + !std::is_same_v, std::remove_cv_t> && + (detail::alternative_type_count>::value == 1)>> { + template + static Target* apply(Factory& factory, Context& context, + type_descriptor requested_type, + type_descriptor registered_type) { + return detail::resolve_alternative_type_pointer( + detail::resolve_source(factory, context), requested_type, + registered_type); + } +}; + +template +struct type_conversion< + StorageTag, Target*, Source&, + std::enable_if_t && + std::is_same_v, + std::remove_cv_t>>> { + template + static Target* apply(Factory& factory, Context& context, type_descriptor, + type_descriptor) { + return std::addressof(detail::resolve_source(factory, context)); + } +}; + +template +struct type_conversion< + StorageTag, Target*, Source*, + std::enable_if_t< + is_alternative_type_v && + !std::is_same_v, std::remove_cv_t> && + (detail::alternative_type_count>::value == 1)>> { + template + static Target* apply(Factory& factory, Context& context, + type_descriptor requested_type, + type_descriptor registered_type) { + return detail::resolve_alternative_type_pointer( + *detail::resolve_source(factory, context), requested_type, + registered_type); + } +}; + +template +struct type_conversion< + StorageTag, Target*, Source*, + std::enable_if_t && + std::is_same_v, + std::remove_cv_t>>> { + template + static Target* apply(Factory& factory, Context& context, type_descriptor, + type_descriptor) { + return detail::resolve_source(factory, context); + } +}; + template struct type_conversion< StorageTag, Target, Source&, @@ -239,12 +477,45 @@ struct type_conversion< } }; +template +struct type_conversion< + StorageTag, Target, Source&, + std::enable_if_t && + detail::is_borrowed_alternative_type_v>> { + template + static Target& apply(Factory& factory, Context& context, type_descriptor, + type_descriptor) { + auto& source = detail::resolve_source(factory, context); + return type_traits::borrow(source); + } +}; + +template +struct type_conversion< + StorageTag, Target, Source&, + std::enable_if_t< + !std::is_pointer_v && + detail::is_borrowed_alternative_type_alternative_v>> { + template + static Target& apply(Factory& factory, Context& context, + type_descriptor requested_type, + type_descriptor registered_type) { + auto& source = detail::resolve_source(factory, context); + return detail::resolve_alternative_type_reference( + type_traits::borrow(source), requested_type, + registered_type); + } +}; + template struct type_conversion< StorageTag, Target, Source&, std::enable_if_t::enabled && !std::is_pointer_v && type_traits::enabled && - type_traits::is_value_borrowable>> { + type_traits::is_value_borrowable && + !detail::is_borrowed_alternative_type_v && + !detail::is_borrowed_alternative_type_alternative_v>> { template static Target& apply(Factory& factory, Context& context, type_descriptor requested_type, @@ -267,12 +538,44 @@ struct type_conversion< } }; +template +struct type_conversion< + StorageTag, Target*, Source&, + std::enable_if_t>> { + template + static Target* apply(Factory& factory, Context& context, type_descriptor, + type_descriptor) { + auto& source = detail::resolve_source(factory, context); + return std::addressof( + static_cast(type_traits::borrow(source))); + } +}; + +template +struct type_conversion< + StorageTag, Target*, Source&, + std::enable_if_t< + detail::is_borrowed_alternative_type_alternative_v>> { + template + static Target* apply(Factory& factory, Context& context, + type_descriptor requested_type, + type_descriptor registered_type) { + auto& source = detail::resolve_source(factory, context); + return detail::resolve_alternative_type_pointer( + type_traits::borrow(source), requested_type, + registered_type); + } +}; + template struct type_conversion< StorageTag, Target*, Source&, std::enable_if_t::enabled && type_traits::enabled && - type_traits::is_value_borrowable>> { + type_traits::is_value_borrowable && + !detail::is_borrowed_alternative_type_v && + !detail::is_borrowed_alternative_type_alternative_v>> { template static Target* apply(Factory& factory, Context& context, type_descriptor requested_type, @@ -357,7 +660,9 @@ struct type_conversion< template struct type_conversion>> { + std::enable_if_t && + !std::is_array_v && + !is_alternative_type_v>> { template static Target* apply(Factory& factory, Context& context, type_descriptor, type_descriptor) { @@ -370,7 +675,7 @@ struct type_conversion< StorageTag, Target, Source*, std::enable_if_t && !std::is_pointer_v && - !std::is_array_v>> { + !std::is_array_v && !is_alternative_type_v>> { template static Target& apply(Factory& factory, Context& context, type_descriptor, type_descriptor) { @@ -381,7 +686,9 @@ struct type_conversion< template struct type_conversion< StorageTag, Target*, Source&, - std::enable_if_t::enabled && !is_pointer_like_type_v>> { + std::enable_if_t::enabled && + !is_pointer_like_type_v && + !is_alternative_type_v>> { template static Target* apply(Factory& factory, Context& context, type_descriptor, type_descriptor) { diff --git a/include/dingo/storage/type_storage_traits.h b/include/dingo/storage/type_storage_traits.h index 0e53571..ee04356 100644 --- a/include/dingo/storage/type_storage_traits.h +++ b/include/dingo/storage/type_storage_traits.h @@ -82,7 +82,7 @@ template struct storage_traits< unique, Type, U, std::enable_if_t::enabled && !std::is_reference_v && - !std::is_array_v>> { + !std::is_array_v && !is_alternative_type_v>> { static constexpr bool enabled = true; using value_types = type_list; @@ -96,7 +96,7 @@ template struct resolution_traits< unique, Type, U, std::enable_if_t::enabled && !std::is_reference_v && - !std::is_array_v>> { + !std::is_array_v && !is_alternative_type_v>> { using value_types = type_list>; using lvalue_reference_types = type_list<>; using rvalue_reference_types = type_list&&>; diff --git a/include/dingo/type/type_traits.h b/include/dingo/type/type_traits.h index 0b922c3..33351d2 100644 --- a/include/dingo/type/type_traits.h +++ b/include/dingo/type/type_traits.h @@ -58,28 +58,131 @@ struct construction_traits { static constexpr bool enabled = false; }; +template struct alternative_type_traits { + static constexpr bool enabled = false; +}; + +template +inline constexpr bool is_alternative_type_v = + alternative_type_traits>::enabled; + namespace detail { -template struct variant_alternative_count; +template struct type_list_count; template -struct variant_alternative_count, Selected> +struct type_list_count, Selected> : std::integral_constant ? 1u : 0u))> {}; + +template struct type_list_has_duplicates; + +template +struct type_list_has_duplicates> + : std::bool_constant< + ((type_list_count, Alternatives>::value > 1) || + ...)> {}; + +template +struct alternative_type_alternatives {}; + +template +struct alternative_type_alternatives< + Type, + std::enable_if_t>::enabled>> { + using type = typename alternative_type_traits>::alternatives; + + static_assert( + !type_list_has_duplicates::value, + "alternative_type_traits::alternatives must not contain duplicate types"); +}; + +template +using alternative_type_alternatives_t = + typename alternative_type_alternatives::type; + +template +struct alternative_type_count : std::integral_constant {}; + +template +struct alternative_type_count< + Type, Selected, + std::enable_if_t>::enabled>> + : type_list_count, + std::remove_cv_t> {}; + +template +struct alternative_type_interface_types {}; + +template +struct alternative_type_interface_types< + Type, + std::enable_if_t>::enabled>> { + using type = + type_list_cat_t>, + alternative_type_alternatives_t>; +}; } // namespace detail -template +template +struct alternative_type_traits> { + static constexpr bool enabled = true; + + using alternatives = type_list; + + template + static std::variant wrap(Value&& value) { + return std::variant(std::in_place_type, + std::forward(value)); + } + + template + static Selected* get(std::variant& value) { + return std::get_if(&value); + } + + template + static const Selected* get(const std::variant& value) { + return std::get_if(&value); + } +}; + +template +inline constexpr bool is_alternative_type_interface_compatible_v = + detail::alternative_type_count, + std::remove_cv_t>::value == 1; + +template +struct construction_traits< + Type, Selected, + std::enable_if_t && + (detail::alternative_type_count::value == + 1)>> { + static constexpr bool enabled = true; + + using type = std::remove_cv_t; + + template static type wrap(Value&& value) { + return alternative_type_traits::template wrap( + std::forward(value)); + } +}; + +template struct construction_traits< - std::variant, Selected, - std::enable_if_t< - detail::variant_alternative_count, - Selected>::value == 1>> { + Type, Selected, + std::enable_if_t::enabled && !std::is_pointer_v && + construction_traits::value_type, + Selected>::enabled>> { static constexpr bool enabled = true; - using type = std::variant; + using value_type = typename type_traits::value_type; + using type = Type; template static type wrap(Value&& value) { - return type(std::in_place_type, std::forward(value)); + return type_traits::make( + construction_traits::wrap( + std::forward(value))); } }; @@ -573,6 +676,10 @@ struct type_traits, if constexpr (type_traits::enabled && !std::is_pointer_v) return std::unique_ptr( new T(detail::make_nested(std::forward(args)...))); + // Work around direct-initialization in smart-pointer-backed factories. + else if constexpr (std::is_constructible_v) + return std::unique_ptr( + new T(std::forward(args)...)); else return std::unique_ptr( new T{std::forward(args)...}); @@ -982,15 +1089,18 @@ struct storage_traits, U> { using conversion_types = type_list<>; }; -template -struct storage_traits, std::variant> { +template +struct storage_traits< + unique, Type, U, + std::enable_if_t::enabled && !std::is_reference_v && + !std::is_array_v && is_alternative_type_v>> { static constexpr bool enabled = true; - using value_types = type_list>; + using value_types = type_list; using lvalue_reference_types = type_list<>; - using rvalue_reference_types = type_list&&>; + using rvalue_reference_types = type_list; using pointer_types = type_list<>; - using conversion_types = type_list>; + using conversion_types = type_list<>; }; namespace detail { diff --git a/test/storage/external.cpp b/test/storage/external.cpp index ec6a9be..7ab4e2b 100644 --- a/test/storage/external.cpp +++ b/test/storage/external.cpp @@ -11,6 +11,7 @@ #include +#include #include #include @@ -20,6 +21,21 @@ #include "support/test.h" namespace dingo { +namespace { +struct shared_ptr_variant_a { + explicit shared_ptr_variant_a(int init) : value(init) {} + int value; +}; + +struct shared_ptr_variant_b { + explicit shared_ptr_variant_b(float init) : value(init) {} + float value; +}; + +using shared_ptr_variant = + std::variant; +} // namespace + template struct external_test : public test {}; TYPED_TEST_SUITE(external_test, container_types, ); @@ -402,11 +418,199 @@ TYPED_TEST(external_test, variant_ref) { .template register_type, storage&>>( c); + auto whole = container.template resolve>(); + ASSERT_TRUE(std::holds_alternative(whole)); + EXPECT_EQ(std::get(whole).value, 7); + auto& value = container.template resolve&>(); ASSERT_EQ(&value, &c); ASSERT_TRUE(std::holds_alternative(value)); EXPECT_EQ(std::get(value).value, 7); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 7); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 7); + EXPECT_EQ(&selected, &std::get(c)); + + auto& selected_const = container.template resolve(); + EXPECT_EQ(selected_const.value, 7); + EXPECT_EQ(&selected_const, &std::get(c)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(c)); + + auto* selected_const_ptr = container.template resolve(); + ASSERT_NE(selected_const_ptr, nullptr); + EXPECT_EQ(selected_const_ptr, &std::get(c)); + + ASSERT_THROW(container.template resolve(), type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(external_test, variant_explicit_interfaces_override_defaults) { + using container_type = TypeParam; + + struct A { + explicit A(int init) : value(init) {} + int value; + }; + struct B { + explicit B(float init) : value(init) {} + float value; + }; + + std::variant c(std::in_place_type, 11); + + container_type container; + container + .template register_type, storage&>, + interfaces>(c); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 11); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 11); + EXPECT_EQ(&selected, &std::get(c)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(c)); + + ASSERT_THROW((container.template resolve>()), + type_not_found_exception); + ASSERT_THROW((container.template resolve&>()), + type_not_found_exception); + ASSERT_THROW((container.template resolve*>()), + type_not_found_exception); + ASSERT_THROW(container.template resolve(), type_not_found_exception); +} + +TYPED_TEST(external_test, shared_ptr_variant_ref) { + using container_type = TypeParam; + + auto handle = std::make_shared( + std::in_place_type, 13); + + container_type container; + container + .template register_type, + storage>>(handle); + + auto whole = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(whole)); + EXPECT_EQ(std::get(whole).value, 13); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value)); + EXPECT_EQ(std::get(value).value, 13); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 13); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 13); + EXPECT_EQ(&selected, &std::get(*handle)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(*handle)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(external_test, unique_ptr_variant_ref) { + using container_type = TypeParam; + + auto handle = std::make_unique( + std::in_place_type, 13); + + container_type container; + container + .template register_type, + storage&>>( + handle); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value)); + EXPECT_EQ(std::get(value).value, 13); + + auto* value_ptr = container.template resolve(); + ASSERT_NE(value_ptr, nullptr); + EXPECT_EQ(value_ptr, &*handle); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 13); + EXPECT_EQ(&selected, &std::get(*handle)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(*handle)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(external_test, unique_ptr_variant_move) { + using container_type = TypeParam; + + auto handle = std::make_unique( + std::in_place_type, 17); + + container_type container; + container + .template register_type, + storage>>( + std::move(handle)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value)); + EXPECT_EQ(std::get(value).value, 17); + + auto* value_ptr = container.template resolve(); + ASSERT_NE(value_ptr, nullptr); + EXPECT_EQ(value_ptr, &value); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 17); + EXPECT_EQ(&selected, &std::get(value)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(value)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); } TYPED_TEST(external_test, shared_multiple) { diff --git a/test/storage/shared.cpp b/test/storage/shared.cpp index eb282b7..c2b2d73 100644 --- a/test/storage/shared.cpp +++ b/test/storage/shared.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -38,8 +39,73 @@ struct pointer_conversion_owner : pointer_conversion_view { pointer_conversion_view* view_; }; + +struct shared_ptr_variant_a { + explicit shared_ptr_variant_a(int init) : value(init) {} + int value; +}; + +struct shared_ptr_variant_b { + explicit shared_ptr_variant_b(float init) : value(init) {} + float value; +}; + +struct custom_sum_a { + explicit custom_sum_a(int init) : value(init) {} + int value; +}; + +struct custom_sum_b { + explicit custom_sum_b(float init) : value(init) {} + float value; +}; + +using shared_ptr_variant = + std::variant; + +struct custom_sum { + std::variant value; +}; + +inline std::shared_ptr make_shared_ptr_variant(int value) { + return std::make_shared( + std::in_place_type, value); +} + +inline std::unique_ptr make_unique_ptr_variant(int value) { + return std::make_unique( + std::in_place_type, value); +} + +inline std::shared_ptr make_shared_custom_sum(int value) { + return std::make_shared( + custom_sum{std::variant( + std::in_place_type, value)}); +} } // namespace +template <> struct alternative_type_traits { + static constexpr bool enabled = true; + + using alternatives = type_list; + + template + static custom_sum wrap(Value&& value) { + return custom_sum{ + std::variant( + std::in_place_type, std::forward(value))}; + } + + template static Selected* get(custom_sum& value) { + return std::get_if(&value.value); + } + + template + static const Selected* get(const custom_sum& value) { + return std::get_if(&value.value); + } +}; + template <> struct type_conversion_traits { @@ -360,9 +426,253 @@ TYPED_TEST(shared_test, variant_factory_selects_alternative) { scope, storage>, factory>>(); + auto whole = container.template resolve>(); + ASSERT_TRUE(std::holds_alternative(whole)); + EXPECT_EQ(std::get(whole).value, 0); + auto& value = container.template resolve&>(); ASSERT_TRUE(std::holds_alternative(value)); EXPECT_EQ(std::get(value).value, 0); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 0); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + EXPECT_EQ(&selected, &std::get(value)); + + auto& selected_const = container.template resolve(); + EXPECT_EQ(selected_const.value, 0); + EXPECT_EQ(&selected_const, &std::get(value)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(value)); + + auto* selected_const_ptr = container.template resolve(); + ASSERT_NE(selected_const_ptr, nullptr); + EXPECT_EQ(selected_const_ptr, &std::get(value)); + + ASSERT_THROW(container.template resolve(), type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(shared_test, variant_explicit_interfaces_override_defaults) { + using container_type = TypeParam; + + struct A { + explicit A(int init) : value(init) {} + int value; + }; + struct B { + explicit B(float init) : value(init) {} + float value; + }; + + container_type container; + container.template register_type, storage>(); + container.template register_type< + scope, storage>, + factory>, interfaces>(); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 0); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr->value, 0); + + ASSERT_THROW((container.template resolve>()), + type_not_found_exception); + ASSERT_THROW((container.template resolve&>()), + type_not_found_exception); + ASSERT_THROW((container.template resolve*>()), + type_not_found_exception); + ASSERT_THROW(container.template resolve(), type_not_found_exception); +} + +TYPED_TEST(shared_test, custom_alternative_type_factory_selects_alternative) { + using container_type = TypeParam; + + container_type container; + container.template register_type, storage>(); + container.template register_type, storage, + factory>>(); + + auto whole = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(whole.value)); + EXPECT_EQ(std::get(whole.value).value, 0); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value.value)); + EXPECT_EQ(std::get(value.value).value, 0); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 0); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + EXPECT_EQ(&selected, &std::get(value.value)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(value.value)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(shared_test, shared_ptr_variant_factory_selects_alternative) { + using container_type = TypeParam; + + container_type container; + container.template register_type, storage>(); + container.template register_type< + scope, storage>, + factory>>(); + + auto whole = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(whole)); + EXPECT_EQ(std::get(whole).value, 0); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value)); + EXPECT_EQ(std::get(value).value, 0); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 0); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + EXPECT_EQ(&selected, &std::get(value)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(value)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(shared_test, shared_ptr_custom_sum_factory_selects_alternative) { + using container_type = TypeParam; + + container_type container; + container.template register_type, storage>(); + container.template register_type, storage>, + factory>>(); + + auto whole = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(whole.value)); + EXPECT_EQ(std::get(whole.value).value, 0); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value.value)); + EXPECT_EQ(std::get(value.value).value, 0); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 0); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + EXPECT_EQ(&selected, &std::get(value.value)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(value.value)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(shared_test, unique_ptr_variant_factory_selects_alternative) { + using container_type = TypeParam; + + container_type container; + container.template register_type, storage>(); + container.template register_type< + scope, storage>, + factory>>(); + + auto whole = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(whole)); + EXPECT_EQ(std::get(whole).value, 0); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value)); + EXPECT_EQ(std::get(value).value, 0); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 0); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + EXPECT_EQ(&selected, &std::get(value)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(value)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); +} + +TYPED_TEST(shared_test, unique_ptr_variant_direct_construction_selects_alternative) { + using container_type = TypeParam; + + container_type container; + container.template register_type, storage>(); + container.template register_type< + scope, storage>, + factory>>(); + + auto whole = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(whole)); + EXPECT_EQ(std::get(whole).value, 0); + + auto& value = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(value)); + EXPECT_EQ(std::get(value).value, 0); + + auto copy = container.template resolve(); + EXPECT_EQ(copy.value, 0); + + auto& selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + EXPECT_EQ(&selected, &std::get(value)); + + auto* selected_ptr = container.template resolve(); + ASSERT_NE(selected_ptr, nullptr); + EXPECT_EQ(selected_ptr, &std::get(value)); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); } TYPED_TEST(shared_test, shared_multiple) { diff --git a/test/storage/unique.cpp b/test/storage/unique.cpp index 24e992f..8b4ae2a 100644 --- a/test/storage/unique.cpp +++ b/test/storage/unique.cpp @@ -19,6 +19,44 @@ #include "support/test.h" namespace dingo { +namespace { +struct custom_sum_a { + explicit custom_sum_a(int init) : value(init) {} + int value; +}; + +struct custom_sum_b { + explicit custom_sum_b(float init) : value(init) {} + float value; +}; + +struct custom_sum { + std::variant value; +}; +} // namespace + +template <> struct alternative_type_traits { + static constexpr bool enabled = true; + + using alternatives = type_list; + + template + static custom_sum wrap(Value&& value) { + return custom_sum{ + std::variant( + std::in_place_type, std::forward(value))}; + } + + template static Selected* get(custom_sum& value) { + return std::get_if(&value.value); + } + + template + static const Selected* get(const custom_sum& value) { + return std::get_if(&value.value); + } +}; + template struct unique_test : public test {}; TYPED_TEST_SUITE(unique_test, container_types, ); @@ -240,10 +278,79 @@ TYPED_TEST(unique_test, variant_factory_selects_alternative) { ASSERT_TRUE(std::holds_alternative(moved)); EXPECT_EQ(std::get(moved).value, 0); - AssertTypeNotFound< - std::variant, - type_list>( - container); + auto selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + + auto selected_const = container.template resolve(); + EXPECT_EQ(selected_const.value, 0); + + auto selected_moved = container.template resolve(); + EXPECT_EQ(selected_moved.value, 0); + + auto selected_const_moved = container.template resolve(); + EXPECT_EQ(selected_const_moved.value, 0); + + ASSERT_THROW(container.template resolve(), type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), type_not_convertible_exception); +} + +TYPED_TEST(unique_test, variant_explicit_interfaces_override_defaults) { + using container_type = TypeParam; + + struct A { + explicit A(int init) : value(init) {} + int value; + }; + struct B { + explicit B(float init) : value(init) {} + float value; + }; + + container_type container; + container.template register_type, storage>(); + container.template register_type< + scope, storage>, + factory>, interfaces>(); + + auto selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + + ASSERT_THROW((container.template resolve>()), + type_not_found_exception); + ASSERT_THROW(container.template resolve(), type_not_found_exception); +} + +TYPED_TEST(unique_test, custom_alternative_type_factory_selects_alternative) { + using container_type = TypeParam; + + container_type container; + container.template register_type, storage>(); + container.template register_type, storage, + factory>>(); + + auto whole = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(whole.value)); + EXPECT_EQ(std::get(whole.value).value, 0); + + auto moved = container.template resolve(); + ASSERT_TRUE(std::holds_alternative(moved.value)); + EXPECT_EQ(std::get(moved.value).value, 0); + + auto selected = container.template resolve(); + EXPECT_EQ(selected.value, 0); + + auto selected_moved = container.template resolve(); + EXPECT_EQ(selected_moved.value, 0); + + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); + ASSERT_THROW(container.template resolve(), + type_not_convertible_exception); } // TODO: shared_ptr needs to be passed as &&