From 22acb7dd7cf501eaaf8053b524bf381f0a627b19 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 16:30:50 +0100 Subject: [PATCH 01/12] First steps in attempting to provide better diagnostic. --- include/tanuki/tanuki.hpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 57470ef..b207b9e 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -414,13 +414,21 @@ struct get_iface_impl { using type = typename IFace::template impl; }; +template +concept with_external_or_intrusive_iface_impl = requires() { typename get_iface_impl::type; }; + // Meta-programming to select the implementation of an interface. // By default, the Base for the interface implementation is // value_iface of IFace (which transitively makes IFace also a base for // the implementation). +template +struct impl_from_iface_impl { +}; + template -struct impl_from_iface_impl : get_iface_impl, Holder, T> { + requires with_external_or_intrusive_iface_impl, Holder, T> +struct impl_from_iface_impl : get_iface_impl, Holder, T> { }; // For composite interfaces, we synthesize a class hierarchy in which every @@ -428,17 +436,35 @@ struct impl_from_iface_impl : get_iface_impl, Hol // derives from value_iface of the composite interface. template struct c_iface_assembler { +}; + +template + requires requires() { + requires with_external_or_intrusive_iface_impl; + typename c_iface_assembler::type, + IFaceN...>::type; + } +struct c_iface_assembler { using cur_impl = typename get_iface_impl::type; using type = typename c_iface_assembler::type; }; template + requires requires() { + requires with_external_or_intrusive_iface_impl; + typename get_iface_impl::type, Holder, + T>::type; + } struct c_iface_assembler { using cur_impl = typename get_iface_impl::type; using type = typename get_iface_impl::type; }; template + requires requires() { + typename c_iface_assembler, Sem>, + IFace1, IFaceN...>::type; + } struct impl_from_iface_impl, Holder, T, Sem> { using type = typename c_iface_assembler, Sem>, @@ -447,6 +473,7 @@ struct impl_from_iface_impl, Holder, // Helper alias. template + requires requires() { typename impl_from_iface_impl::type; } using impl_from_iface = typename impl_from_iface_impl::type; // Concept to check that the interface IFace has an implementation From 0666db6be5508be21a058c2248b4ec1ecc4a70fc Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 16:47:13 +0100 Subject: [PATCH 02/12] Tentative MSVC fix. --- include/tanuki/tanuki.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index b207b9e..c01fbd3 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -409,7 +409,8 @@ struct get_iface_impl { // Intrusive interface implementation. template - requires iface_has_intrusive_impl && (!iface_has_external_impl) + requires(!is_composite_interface_v) + && iface_has_intrusive_impl && (!iface_has_external_impl) struct get_iface_impl { using type = typename IFace::template impl; }; From 452b9e58ccd44ebd5ead8627e10f6b60756aedd5 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 17:30:10 +0100 Subject: [PATCH 03/12] Internal doc bits, clarificatory renaming. --- include/tanuki/tanuki.hpp | 59 +++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index c01fbd3..faca9c5 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -385,51 +385,68 @@ struct iface_impl final : detail::iface_impl_base { namespace detail { -// Detect the presence of an external interface implementation. +// NOTE: this section contains the metaprogramming necessary to determine whether or +// not an interface has an implementation, and to automatically synthesise composite +// interface implementations. + +// Detect the presence of an external or intrusive interface implementation. +// NOTE: at this stage, we are only checking for the existence of a specialisation +// of iface_impl (external) or an 'impl' typedef (intrusive). Further checks are +// implemented later. template concept iface_has_external_impl = !std::derived_from, iface_impl_base>; -// Detect the presence of an intrusive interface implementation. template concept iface_has_intrusive_impl = requires() { typename IFace::template impl; }; -// Helper to fetch the interface implementation. +// Helper to fetch the implementation of a non-composite interface template -struct get_iface_impl { +struct get_nc_iface_impl { }; // External interface implementation. // NOTE: this will take the precedence in case an intrusive // implementation is also available. template - requires iface_has_external_impl -struct get_iface_impl { + requires + // NOTE: this concept also indirectly checks that IFace + // is not composite, as composite interfaces are prevented from + // having external implementations. + iface_has_external_impl +struct get_nc_iface_impl { using type = iface_impl; }; // Intrusive interface implementation. template - requires(!is_composite_interface_v) - && iface_has_intrusive_impl && (!iface_has_external_impl) -struct get_iface_impl { + requires + // NOTE: we might end up instantiating this struct with a composite + // IFace when doing concept checking in impl_from_iface_impl. This seems + // to confuse some compilers (e.g., MSVC) since the composite interface may + // have several 'impl' typedefs inherited from the individual interfaces. + // Prevent this issue by disabling this specialisation if IFace is a composite + // interface. + (!is_composite_interface_v) && iface_has_intrusive_impl + && (!iface_has_external_impl) + struct get_nc_iface_impl { using type = typename IFace::template impl; }; template -concept with_external_or_intrusive_iface_impl = requires() { typename get_iface_impl::type; }; +concept with_external_or_intrusive_iface_impl + = requires() { typename get_nc_iface_impl::type; }; // Meta-programming to select the implementation of an interface. - -// By default, the Base for the interface implementation is -// value_iface of IFace (which transitively makes IFace also a base for -// the implementation). template struct impl_from_iface_impl { }; +// For non-composite interfaces, the Base for the interface implementation is +// value_iface of IFace (which transitively makes IFace also a base for +// the implementation). template requires with_external_or_intrusive_iface_impl, Holder, T> -struct impl_from_iface_impl : get_iface_impl, Holder, T> { +struct impl_from_iface_impl : get_nc_iface_impl, Holder, T> { }; // For composite interfaces, we synthesize a class hierarchy in which every @@ -442,23 +459,23 @@ struct c_iface_assembler { template requires requires() { requires with_external_or_intrusive_iface_impl; - typename c_iface_assembler::type, + typename c_iface_assembler::type, IFaceN...>::type; } struct c_iface_assembler { - using cur_impl = typename get_iface_impl::type; + using cur_impl = typename get_nc_iface_impl::type; using type = typename c_iface_assembler::type; }; template requires requires() { requires with_external_or_intrusive_iface_impl; - typename get_iface_impl::type, Holder, - T>::type; + typename get_nc_iface_impl::type, Holder, + T>::type; } struct c_iface_assembler { - using cur_impl = typename get_iface_impl::type; - using type = typename get_iface_impl::type; + using cur_impl = typename get_nc_iface_impl::type; + using type = typename get_nc_iface_impl::type; }; template From 9585d18ab67237cf9ec52829ce04d8e490814377 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 17:56:38 +0100 Subject: [PATCH 04/12] More WIP, checking CI. --- include/tanuki/tanuki.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index faca9c5..5fe58a7 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -724,6 +724,12 @@ struct holder_value> { } // namespace detail +template +concept iface_with_impl + = detail::iface_has_impl, T, wrap_semantics::value> + && detail::iface_has_impl, T, + wrap_semantics::reference>; + namespace detail { @@ -768,13 +774,15 @@ struct config_base { // that size/alignment are the same regardless of semantics (which should always // be the case). template - requires(sizeof(detail::holder) - == sizeof(detail::holder)) + requires iface_with_impl + && (sizeof(detail::holder) + == sizeof(detail::holder)) inline constexpr auto holder_size = sizeof(detail::holder); template - requires(alignof(detail::holder) - == alignof(detail::holder)) + requires iface_with_impl + && (alignof(detail::holder) + == alignof(detail::holder)) inline constexpr auto holder_align = alignof(detail::holder); // Default implementation of the reference interface. From d45749098564d0c92639d86a1f8eefa7d3635d89 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 18:08:47 +0100 Subject: [PATCH 05/12] Testing bits. --- test/test_basics.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/test_basics.cpp b/test/test_basics.cpp index 054a289..aca3b21 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -51,6 +51,9 @@ template struct any_iface_impl : Base { }; +template +concept with_holder_size = requires() { requires(tanuki::holder_size > 0u); }; + struct large { std::array buffer = {1, 2, 3}; std::string str = "hello world "; @@ -188,6 +191,16 @@ TEST_CASE("basics") const wrap_t wfunc2(&my_func); REQUIRE(value_isa(wfunc1)); REQUIRE(value_isa(wfunc2)); + + // Minimal test for iface_with_impl. + REQUIRE(!tanuki::iface_with_impl); + REQUIRE(!tanuki::iface_with_impl); + REQUIRE(!tanuki::iface_with_impl); + + // Test that concept checking on holder_size fails + // if an interface does not have an implementation for + // a type. + REQUIRE(!with_holder_size); } TEST_CASE("assignment") From bb447dcc1925186f336a9f6583d9cb28549f22ef Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 18:10:00 +0100 Subject: [PATCH 06/12] Ditto. --- test/test_basics.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_basics.cpp b/test/test_basics.cpp index aca3b21..545c314 100644 --- a/test/test_basics.cpp +++ b/test/test_basics.cpp @@ -54,6 +54,9 @@ struct any_iface_impl : Base { template concept with_holder_size = requires() { requires(tanuki::holder_size > 0u); }; +template +concept with_holder_align = requires() { requires(tanuki::holder_align > 0u); }; + struct large { std::array buffer = {1, 2, 3}; std::string str = "hello world "; @@ -201,6 +204,9 @@ TEST_CASE("basics") // if an interface does not have an implementation for // a type. REQUIRE(!with_holder_size); + + // Same on holder_align. + REQUIRE(!with_holder_align); } TEST_CASE("assignment") From 082d91e04152f70b1529ff2ced254762b6d388a2 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 18:24:57 +0100 Subject: [PATCH 07/12] More tweaks. --- include/tanuki/tanuki.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 5fe58a7..6e5ac8a 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -324,10 +324,6 @@ template concept noncv_rvalue_reference = std::is_rvalue_reference_v && std::same_as, std::remove_reference_t>; -// NOTE: constrain value types to be non-cv qualified destructible objects. -template -concept valid_value_type = std::is_object_v && (!std::is_const_v)&&(!std::is_volatile_v)&&std::destructible; - #if defined(__clang__) #pragma GCC diagnostic push @@ -356,6 +352,10 @@ struct TANUKI_VISIBLE composite_iface : public IFace0, public IFace1, public IFa template concept any_wrap = detail::is_any_wrap_v; +// Concept checking for value types. Must be non-cv qualified destructible objects. +template +concept valid_value_type = std::is_object_v && (!std::is_const_v)&&(!std::is_volatile_v)&&std::destructible; + namespace detail { @@ -498,6 +498,8 @@ using impl_from_iface = typename impl_from_iface_impl::ty // for the value type T. template concept iface_has_impl = requires() { + // NOTE: include the check on the validity of the value type. + requires valid_value_type; typename impl_from_iface; // NOTE: this will check that the implementation derives // from its Base (e.g., the check will fail in case of @@ -809,7 +811,7 @@ enum class wrap_ctor { always_explicit, ref_implicit, always_implicit }; // NOTE: the DefaultValueType is subject to the constraints // for valid value types. template - requires std::same_as || detail::valid_value_type + requires std::same_as || valid_value_type struct TANUKI_VISIBLE config final : detail::config_base { using default_value_type = DefaultValueType; @@ -1029,8 +1031,6 @@ class TANUKI_VISIBLE wrap // destructible. std::default_initializable, T, Cfg.semantics>> && std::destructible, T, Cfg.semantics>> && - // T must be a valid value type. - detail::valid_value_type && // T must be constructible from the construction arguments. std::constructible_from void ctor_impl(U &&...x) noexcept(Cfg.semantics == wrap_semantics::value From 14a1d5f919a2314d44e52844ba6820b66f764bfe Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 21:11:51 +0100 Subject: [PATCH 08/12] Minor. --- include/tanuki/tanuki.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 6e5ac8a..f8c1e64 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -1019,7 +1019,7 @@ class TANUKI_VISIBLE wrap // Implementation of generic construction. This will constrcut // a holder with value type T using the construction argument(s) x. // NOTE: it is important that the checks on the - // interface implementation come *before* the checks on T. + // interface implementation come *before* the std::constructible_from check. // This helps breaking infinite recursions that can arise when // the generic constructor of wrap is implicit // (see also the test_inf_loop_bug test). From 27f816a892ce00ec60d53950398f3c74493caa1d Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 21:39:58 +0100 Subject: [PATCH 09/12] Setp 0 in trying to improve concept checking around ctor_impl(). --- include/tanuki/tanuki.hpp | 117 ++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index f8c1e64..25670c9 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -949,6 +949,17 @@ template struct wrap_pointer_iface { }; +template +concept wrap_constructible_from = + // The interface must have an implementation for T. + iface_has_impl, T, Sem> && + // The implementation must be default-initable and + // destructible. + std::default_initializable, T, Sem>> + && std::destructible, T, Sem>> && + // T must be constructible from the construction arguments. + std::constructible_from; + } // namespace detail // Tag structure to construct/assign a wrap @@ -1024,19 +1035,10 @@ class TANUKI_VISIBLE wrap // the generic constructor of wrap is implicit // (see also the test_inf_loop_bug test). template - requires - // The interface must have an implementation for T. - detail::iface_has_impl, T, Cfg.semantics> && - // The implementation must be default-initable and - // destructible. - std::default_initializable, T, Cfg.semantics>> - && std::destructible, T, Cfg.semantics>> && - // T must be constructible from the construction arguments. - std::constructible_from - void ctor_impl(U &&...x) noexcept(Cfg.semantics == wrap_semantics::value - && sizeof(holder_t) <= Cfg.static_size - && alignof(holder_t) <= Cfg.static_align - && std::is_nothrow_constructible_v, U &&...>) + requires detail::wrap_constructible_from + void ctor_impl(U &&...x) noexcept(Cfg.semantics == wrap_semantics::value && sizeof(holder_t) <= Cfg.static_size + && alignof(holder_t) <= Cfg.static_align + && std::is_nothrow_constructible_v, U &&...>) { if constexpr (Cfg.semantics == wrap_semantics::value) { if constexpr (sizeof(holder_t) > Cfg.static_size || alignof(holder_t) > Cfg.static_align) { @@ -1212,34 +1214,29 @@ class TANUKI_VISIBLE wrap // I.e., trailing-style concept checks may not short // circuit, which is particularly problematic for a default // constructor. - template - requires(requires(W &w) { - requires !Cfg.invalid_default_ctor; - requires std::default_initializable; - // A default value type must have been specified - // in the configuration. - requires !std::same_as; - // We must be able to invoke the construction function. - w.template ctor_impl(); - }) + template + requires(!Cfg.invalid_default_ctor) && std::default_initializable && + // A default value type must have been specified + // in the configuration. + (!std::same_as) && + // We must be able to invoke the construction function. + detail::wrap_constructible_from wrap() noexcept(noexcept(this->ctor_impl()) && detail::nothrow_default_initializable) { ctor_impl(); } // Generic ctor from a wrappable value. - template - requires(requires(W &w, T &&x) { - requires std::default_initializable; - // Make extra sure this does not compete with the invalid ctor. - requires !std::same_as>; - // Must not compete with the emplace ctor. - requires !detail::is_in_place_type_v>; - // Must not compete with copy/move. - requires !std::same_as, W>; - // We must be able to invoke the construction function. - w.template ctor_impl>(std::forward(x)); - }) + template + requires std::default_initializable && + // Make extra sure this does not compete with the invalid ctor. + (!std::same_as>) && + // Must not compete with the emplace ctor. + (!detail::is_in_place_type_v>) && + // Must not compete with copy/move. + (!std::same_as, wrap>) && + // We must be able to invoke the construction function. + detail::wrap_constructible_from, iface_t, Cfg.semantics, T &&> explicit(explicit_ctor < wrap_ctor::always_implicit) // NOLINTNEXTLINE(bugprone-forwarding-reference-overload,google-explicit-constructor,hicpp-explicit-conversions) wrap(T &&x) noexcept(noexcept(this->ctor_impl>(std::forward(x))) @@ -1252,12 +1249,11 @@ class TANUKI_VISIBLE wrap // NOTE: this is implemented separately from the generic ctor // only in order to work around compiler bugs when the explicit() // clause contains complex expressions. - template - requires(requires(W &w, std::reference_wrapper ref) { - requires std::default_initializable; - // We must be able to invoke the construction function. - w.template ctor_impl>(std::move(ref)); - }) + template + requires std::default_initializable && + // We must be able to invoke the construction function. + detail::wrap_constructible_from, iface_t, Cfg.semantics, + std::reference_wrapper> explicit(explicit_ctor == wrap_ctor::always_explicit) // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) wrap(std::reference_wrapper ref) noexcept( @@ -1270,13 +1266,12 @@ class TANUKI_VISIBLE wrap // Generic in-place initialisation. // NOTE: this will *value-init* if no args // are provided. This must be documented well. - template - requires(requires(W &w, U &&...args) { - requires std::default_initializable; - // Forbid emplacing a wrap inside a wrap. - requires !std::same_as; - w.template ctor_impl(std::forward(args)...); - }) + template + requires std::default_initializable && + // Forbid emplacing a wrap inside a wrap. + (!std::same_as) && + // We must be able to invoke the construction function. + detail::wrap_constructible_from explicit wrap(std::in_place_type_t, U &&...args) noexcept(noexcept(this->ctor_impl(std::forward(args)...)) && detail::nothrow_default_initializable) { @@ -1474,19 +1469,19 @@ class TANUKI_VISIBLE wrap } // Generic assignment. - template - requires(requires(W &w, T &&x) { - // NOTE: not 100% sure about this, but it seems consistent - // for generic assignment to be enabled only if copy/move - // assignment are as well. - requires(Cfg.copyable && Cfg.movable) || Cfg.semantics == wrap_semantics::reference; - // Make extra sure this does not compete with the invalid assignment operator. - requires !std::same_as>; - // Must not compete with copy/move assignment. - requires !std::same_as, W>; - w.template ctor_impl>(std::forward(x)); - }) - wrap &operator=(T &&x) + template + requires + // NOTE: not 100% sure about this, but it seems consistent + // for generic assignment to be enabled only if copy/move + // assignment are as well. + ((Cfg.copyable && Cfg.movable) || Cfg.semantics == wrap_semantics::reference) && + // Make extra sure this does not compete with the invalid assignment operator. + (!std::same_as>) && + // Must not compete with copy/move assignment. + (!std::same_as, wrap>) && + // We must be able to invoke the construction function. + detail::wrap_constructible_from, iface_t, Cfg.semantics, T &&> + wrap &operator=(T &&x) { if constexpr (Cfg.semantics == wrap_semantics::value) { // Handle invalid object. From 33b69c1cc0b602ae80a7f7a2d46c5d8046da6663 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 21:52:05 +0100 Subject: [PATCH 10/12] Another simplifcation attempt. --- include/tanuki/tanuki.hpp | 42 ++++++++++----------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index 25670c9..c138b76 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -949,6 +949,11 @@ template struct wrap_pointer_iface { }; +// NOTE: it is important that the checks on the +// interface implementation come *before* the std::constructible_from check. +// This helps breaking infinite recursions that can arise when +// the generic constructor of wrap is implicit +// (see also the test_inf_loop_bug test). template concept wrap_constructible_from = // The interface must have an implementation for T. @@ -1029,13 +1034,7 @@ class TANUKI_VISIBLE wrap // Implementation of generic construction. This will constrcut // a holder with value type T using the construction argument(s) x. - // NOTE: it is important that the checks on the - // interface implementation come *before* the std::constructible_from check. - // This helps breaking infinite recursions that can arise when - // the generic constructor of wrap is implicit - // (see also the test_inf_loop_bug test). template - requires detail::wrap_constructible_from void ctor_impl(U &&...x) noexcept(Cfg.semantics == wrap_semantics::value && sizeof(holder_t) <= Cfg.static_size && alignof(holder_t) <= Cfg.static_align && std::is_nothrow_constructible_v, U &&...>) @@ -1541,32 +1540,13 @@ class TANUKI_VISIBLE wrap #endif // Emplacement. - // NOTE: this is a mess on earlier clang/GCC versions... -#if defined(__clang__) - template - requires(requires(W &w, Args &&...args) { - requires std::is_same_v; - // Forbid emplacing a wrap inside a wrap. - requires(!std::is_same_v); - w.template ctor_impl(std::forward(args)...); - }) - friend void - emplace(W &w, - Args &&...args) noexcept(noexcept(std::declval().template ctor_impl(std::forward(args)...))) -#elif defined(__GNUC__) - template , int> = 0, typename... Args> - requires(requires(wrap &w, Args &&...args) { w.ctor_impl(std::forward(args)...); }) - friend void emplace(wrap &w, Args &&...args) noexcept(noexcept(w.ctor_impl(std::forward(args)...))) -#else - // This should be the canonical version. template - requires(requires(wrap &w, Args &&...args) { - // Forbid emplacing a wrap inside a wrap. - requires !std::same_as; - w.ctor_impl(std::forward(args)...); - }) - friend void emplace(wrap &w, Args &&...args) noexcept(noexcept(w.ctor_impl(std::forward(args)...))) -#endif + requires + // Forbid emplacing a wrap inside a wrap. + (!std::same_as) && + // We must be able to invoke the construction function. + detail::wrap_constructible_from + friend void emplace(wrap &w, Args &&...args) noexcept(noexcept(w.ctor_impl(std::forward(args)...))) { if constexpr (Cfg.semantics == wrap_semantics::value) { // Destroy the value in w if necessary. From 6daa10f19700752c79efab1eec16aa4c0baad90e Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Sun, 17 Mar 2024 22:52:11 +0100 Subject: [PATCH 11/12] Tweaks. --- include/tanuki/tanuki.hpp | 61 +++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index c138b76..bcea82e 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -512,7 +512,7 @@ concept iface_has_impl = requires() { // holder -> iface impl -> value_iface -> iface. // The holder class implements the value_iface interface. // NOTE: this class has several conceptual requirements which -// are checked in wrap::ctor_impl(). +// are checked in the generic ctors of the wrap class. template struct TANUKI_VISIBLE holder final : public impl_from_iface, T, Sem> { TANUKI_NO_UNIQUE_ADDRESS T m_value; @@ -726,6 +726,12 @@ struct holder_value> { } // namespace detail +// Concept to check that the interface IFace has an implementation +// for the value type T. +// NOTE: like in the holder_size helpers, we check that the implementation +// exists for both semantics types. At this time it is not possible +// for an implementation to exist only for one semantics type and hence +// the double check is superfluous, but let us just keep it for consistency. template concept iface_with_impl = detail::iface_has_impl, T, wrap_semantics::value> @@ -949,19 +955,22 @@ template struct wrap_pointer_iface { }; +// Concept for checking that we can construct a holder object +// for the interface IFace containing the value type T via the +// construction arguments U. +// // NOTE: it is important that the checks on the // interface implementation come *before* the std::constructible_from check. // This helps breaking infinite recursions that can arise when // the generic constructor of wrap is implicit // (see also the test_inf_loop_bug test). template -concept wrap_constructible_from = +concept holder_constructible_from = // The interface must have an implementation for T. iface_has_impl, T, Sem> && - // The implementation must be default-initable and - // destructible. - std::default_initializable, T, Sem>> - && std::destructible, T, Sem>> && + // The implementation must be default-initable (this also implies + // destructability). + std::default_initializable, T, Sem>> && // T must be constructible from the construction arguments. std::constructible_from; @@ -1034,6 +1043,8 @@ class TANUKI_VISIBLE wrap // Implementation of generic construction. This will constrcut // a holder with value type T using the construction argument(s) x. + // NOTE: the requirements for the construction of the holder object + // are in the holder_constructible_from concept. template void ctor_impl(U &&...x) noexcept(Cfg.semantics == wrap_semantics::value && sizeof(holder_t) <= Cfg.static_size && alignof(holder_t) <= Cfg.static_align @@ -1201,25 +1212,19 @@ class TANUKI_VISIBLE wrap // Default initialisation into the default value type. // NOTE: need to document that this value-inits. - // NOTE: the extra W template argument appearing - // here and in the other ctors/assignment operators - // is to work around compiler issues: earlier versions - // of clang error out complaining about an incomplete type - // if we just use wrap instead of W. Older clang versions - // also suffer from this bug + // NOTE: the extra default template parameter is a workaround + // for older clang versions: // // https://github.com/llvm/llvm-project/issues/55945 // - // I.e., trailing-style concept checks may not short - // circuit, which is particularly problematic for a default - // constructor. + // I.e., trailing-style concept checks may not short circuit. template requires(!Cfg.invalid_default_ctor) && std::default_initializable && // A default value type must have been specified // in the configuration. (!std::same_as) && - // We must be able to invoke the construction function. - detail::wrap_constructible_from + // We must be able to construct the holder. + detail::holder_constructible_from wrap() noexcept(noexcept(this->ctor_impl()) && detail::nothrow_default_initializable) { ctor_impl(); @@ -1234,8 +1239,8 @@ class TANUKI_VISIBLE wrap (!detail::is_in_place_type_v>) && // Must not compete with copy/move. (!std::same_as, wrap>) && - // We must be able to invoke the construction function. - detail::wrap_constructible_from, iface_t, Cfg.semantics, T &&> + // We must be able to construct the holder. + detail::holder_constructible_from, iface_t, Cfg.semantics, T &&> explicit(explicit_ctor < wrap_ctor::always_implicit) // NOLINTNEXTLINE(bugprone-forwarding-reference-overload,google-explicit-constructor,hicpp-explicit-conversions) wrap(T &&x) noexcept(noexcept(this->ctor_impl>(std::forward(x))) @@ -1250,9 +1255,9 @@ class TANUKI_VISIBLE wrap // clause contains complex expressions. template requires std::default_initializable && - // We must be able to invoke the construction function. - detail::wrap_constructible_from, iface_t, Cfg.semantics, - std::reference_wrapper> + // We must be able to construct the holder. + detail::holder_constructible_from, iface_t, Cfg.semantics, + std::reference_wrapper> explicit(explicit_ctor == wrap_ctor::always_explicit) // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) wrap(std::reference_wrapper ref) noexcept( @@ -1269,8 +1274,8 @@ class TANUKI_VISIBLE wrap requires std::default_initializable && // Forbid emplacing a wrap inside a wrap. (!std::same_as) && - // We must be able to invoke the construction function. - detail::wrap_constructible_from + // We must be able to construct the holder. + detail::holder_constructible_from explicit wrap(std::in_place_type_t, U &&...args) noexcept(noexcept(this->ctor_impl(std::forward(args)...)) && detail::nothrow_default_initializable) { @@ -1478,8 +1483,8 @@ class TANUKI_VISIBLE wrap (!std::same_as>) && // Must not compete with copy/move assignment. (!std::same_as, wrap>) && - // We must be able to invoke the construction function. - detail::wrap_constructible_from, iface_t, Cfg.semantics, T &&> + // We must be able to construct the holder. + detail::holder_constructible_from, iface_t, Cfg.semantics, T &&> wrap &operator=(T &&x) { if constexpr (Cfg.semantics == wrap_semantics::value) { @@ -1544,8 +1549,8 @@ class TANUKI_VISIBLE wrap requires // Forbid emplacing a wrap inside a wrap. (!std::same_as) && - // We must be able to invoke the construction function. - detail::wrap_constructible_from + // We must be able to construct the holder. + detail::holder_constructible_from friend void emplace(wrap &w, Args &&...args) noexcept(noexcept(w.ctor_impl(std::forward(args)...))) { if constexpr (Cfg.semantics == wrap_semantics::value) { From b0fce78e40c312ef51318edbf1b8a7ff08b804b2 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Mon, 18 Mar 2024 08:28:16 +0100 Subject: [PATCH 12/12] Tentative simplification tweak. --- include/tanuki/tanuki.hpp | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/include/tanuki/tanuki.hpp b/include/tanuki/tanuki.hpp index bcea82e..03509fb 100644 --- a/include/tanuki/tanuki.hpp +++ b/include/tanuki/tanuki.hpp @@ -408,27 +408,15 @@ struct get_nc_iface_impl { // NOTE: this will take the precedence in case an intrusive // implementation is also available. template - requires - // NOTE: this concept also indirectly checks that IFace - // is not composite, as composite interfaces are prevented from - // having external implementations. - iface_has_external_impl + requires iface_has_external_impl struct get_nc_iface_impl { using type = iface_impl; }; // Intrusive interface implementation. template - requires - // NOTE: we might end up instantiating this struct with a composite - // IFace when doing concept checking in impl_from_iface_impl. This seems - // to confuse some compilers (e.g., MSVC) since the composite interface may - // have several 'impl' typedefs inherited from the individual interfaces. - // Prevent this issue by disabling this specialisation if IFace is a composite - // interface. - (!is_composite_interface_v) && iface_has_intrusive_impl - && (!iface_has_external_impl) - struct get_nc_iface_impl { + requires iface_has_intrusive_impl && (!iface_has_external_impl) +struct get_nc_iface_impl { using type = typename IFace::template impl; }; @@ -445,8 +433,15 @@ struct impl_from_iface_impl { // value_iface of IFace (which transitively makes IFace also a base for // the implementation). template - requires with_external_or_intrusive_iface_impl, Holder, T> -struct impl_from_iface_impl : get_nc_iface_impl, Holder, T> { + requires + // NOTE: we add an initial concept check on IFace here in order to avoid + // instantiating the with_external_or_intrusive_iface_impl concept with a + // composite interface. This seems to confuse some compilers (e.g., MSVC) + // since the composite interface may have several 'impl' typedefs inherited + // from the individual interfaces. + (!is_composite_interface_v) + && with_external_or_intrusive_iface_impl, Holder, T> + struct impl_from_iface_impl : get_nc_iface_impl, Holder, T> { }; // For composite interfaces, we synthesize a class hierarchy in which every @@ -728,7 +723,7 @@ struct holder_value> { // Concept to check that the interface IFace has an implementation // for the value type T. -// NOTE: like in the holder_size helpers, we check that the implementation +// NOTE: like in the holder_size helper, we check that the implementation // exists for both semantics types. At this time it is not possible // for an implementation to exist only for one semantics type and hence // the double check is superfluous, but let us just keep it for consistency.