diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index fa551ff3..aef7690e 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -738,6 +738,14 @@ namespace detail { template struct use_class_aux; +// Classes must be complete when they are registered. Without this check, an +// incomplete class fails in the type traits that compute the inheritance map. +template +constexpr bool is_complete = false; + +template +constexpr bool is_complete> = true; + template struct use_class_aux> : std::conditional_t< @@ -826,6 +834,10 @@ using use_classes_tuple_type = boost::mp11::mp_apply< //! @see [Registries and Policies](xref:ROOT:registries_and_policies.adoc) template class use_classes { + static_assert( + (detail::is_complete && ...), + "use_classes: a class is incomplete"); + detail::use_classes_tuple_type tuple; }; @@ -1218,6 +1230,10 @@ inline auto final_virtual_ptr(Arg&& obj) { //! @see [Performance](xref:ROOT:performance.adoc) template class virtual_ptr { + static_assert( + std::is_class_v, + "virtual_ptr<> takes a class or a smart pointer: virtual_ptr, " + "not virtual_ptr"); using traits = virtual_traits; @@ -2389,17 +2405,24 @@ struct validate_method_parameter< false_t, "registry mismatch"); }; -template +// A `virtual_ptr` passed by non-const lvalue reference cannot bind to the +// `virtual_ptr` of a derived class, so it cannot have overriders for one, and +// the dispatch treats it as the class of the object. By rvalue reference, it is +// not a virtual parameter at all. +template struct validate_method_parameter< - virtual_ptr&, Registry, void> : std::true_type { - static_assert(check_affinity::value); + virtual_ptr&, MethodRegistry, void> : std::false_type { + static_assert( + false_t, + "virtual_ptr<> must be passed by value or by const reference"); }; template struct validate_method_parameter< - virtual_ptr&, MethodRegistry, void> : std::false_type { + virtual_ptr&&, MethodRegistry, void> : std::false_type { static_assert( - false_t, "registry mismatch"); + false_t, + "virtual_ptr<> must be passed by value or by const reference"); }; template @@ -3138,9 +3161,55 @@ struct validate_overrider_parameter< template struct validate_overrider_parameter : std::true_type {}; +// Whether an overrider parameter would grant write access to an object that the +// method parameter grants read access to only. `P` and `Q` are the classes the +// two parameters designate, cv-qualified: the referent of a reference or a +// pointer, the `element_type` of a `virtual_ptr`. Without this check, the +// overrider is rejected by a `static_cast` or a `dynamic_cast` deep inside the +// thunk. +template +constexpr bool discards_const = std::is_const_v

&& !std::is_const_v; + +template +using designated_class = std::remove_pointer_t>; + +// Whether two `virtual_ptr`s, one in a method, the other in an overrider, hold +// the same kind of pointer: both a plain pointer, or both the same smart +// pointer template. Without this check, a plain `virtual_ptr` in the method and +// a `shared_virtual_ptr` in the overrider fail on a conversion inside the thunk. +template +constexpr auto same_pointer_kind() -> bool { + if constexpr (IsSmartPtr != IsSmartPtr) { + return false; + } else if constexpr (!IsSmartPtr) { + return true; + } else { + return std::is_same_v< + typename virtual_traits::template rebind< + typename T2::element_type>, + T2>; + } +} + +// Whether an overrider returning `From` can implement a method returning `To`. +template +constexpr bool returns_compatible = + std::is_void_v ? std::is_void_v : std::is_convertible_v; + template struct validate_overrider_parameter, T2, void> : - std::true_type {}; + std::true_type { + using P = designated_class; + using Q = designated_class; + + // Only between classes of the same hierarchy: an interop parameter such as + // `virtual_` rejects a mutable overrider parameter itself. + static_assert( + !std::conjunction_v< + std::bool_constant>, + std::is_base_of, std::remove_cv_t>>, + "overrider parameter discards const qualifier"); +}; template struct validate_overrider_parameter, virtual_, void> : @@ -3156,6 +3225,14 @@ template struct validate_overrider_parameter< virtual_ptr, virtual_ptr, void> : std::true_type { static_assert(std::is_same_v, "registry mismatch"); + static_assert( + same_pointer_kind(), + "different virtual_ptr<> pointer kinds"); + static_assert( + !discards_const< + typename virtual_ptr::element_type, + typename virtual_ptr::element_type>, + "overrider parameter discards const qualifier"); using C1 = virtual_type, R>; using C2 = virtual_type, R>; static_assert( @@ -3170,6 +3247,14 @@ struct validate_overrider_parameter< const virtual_ptr&, const virtual_ptr&, void> : std::true_type { static_assert(std::is_same_v, "registry mismatch"); + static_assert( + same_pointer_kind(), + "different virtual_ptr<> pointer kinds"); + static_assert( + !discards_const< + typename virtual_ptr::element_type, + typename virtual_ptr::element_type>, + "overrider parameter discards const qualifier"); using C1 = virtual_type&, R>; using C2 = virtual_type&, R>; static_assert( @@ -3183,6 +3268,14 @@ template struct validate_overrider_parameter< virtual_ptr&&, virtual_ptr&&, void> : std::true_type { static_assert(std::is_same_v, "registry mismatch"); + static_assert( + same_pointer_kind(), + "different virtual_ptr<> pointer kinds"); + static_assert( + !discards_const< + typename virtual_ptr::element_type, + typename virtual_ptr::element_type>, + "overrider parameter discards const qualifier"); using C1 = virtual_type&&, R>; using C2 = virtual_type&&, R>; static_assert( @@ -3272,6 +3365,9 @@ auto method:: (validate_overrider_parameter::value && ...), "virtual_ptr category mismatch"); + static_assert( + returns_compatible, + "overrider return type is not convertible to method return type"); return Overrider( detail::parameter_traits::template cast< diff --git a/test/compile_fail_classes_incomplete.cpp b/test/compile_fail_classes_incomplete.cpp new file mode 100644 index 00000000..b77dc988 --- /dev/null +++ b/test/compile_fail_classes_incomplete.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: use_classes: a class is incomplete + +#include + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog; + +// Without the check, this fails in the type traits that compute the +// inheritance relationships. +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +int main() { + return 0; +} diff --git a/test/compile_fail_overrider_discards_const_reference.cpp b/test/compile_fail_overrider_discards_const_reference.cpp new file mode 100644 index 00000000..b0122ff9 --- /dev/null +++ b/test/compile_fail_overrider_discards_const_reference.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: overrider parameter discards const qualifier + +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (virtual_), void); + +// The method gives read access to the animal, the overrider asks for write +// access. Without the check, this fails inside the thunk, on a `dynamic_cast` +// that casts away constness. +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog&), void) { +} + +int main() { + return 0; +} diff --git a/test/compile_fail_overrider_discards_const_virtual_ptr.cpp b/test/compile_fail_overrider_discards_const_virtual_ptr.cpp new file mode 100644 index 00000000..6054a2f2 --- /dev/null +++ b/test/compile_fail_overrider_discards_const_virtual_ptr.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: overrider parameter discards const qualifier + +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +// The method gives read access to the animal, the overrider asks for write +// access. Without the check, this fails inside the thunk, on a `dynamic_cast` +// that casts away constness. +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr), void) { +} + +int main() { + return 0; +} diff --git a/test/compile_fail_overrider_pointer_kind.cpp b/test/compile_fail_overrider_pointer_kind.cpp new file mode 100644 index 00000000..ae4e9fc1 --- /dev/null +++ b/test/compile_fail_overrider_pointer_kind.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: different virtual_ptr<> pointer kinds + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +// A `virtual_ptr` converts to a `virtual_ptr`, so the overrider +// finds the method, but the method has no `shared_ptr` to give it. Without the +// check, this fails on a conversion inside the thunk. +BOOST_OPENMETHOD_OVERRIDE(poke, (shared_virtual_ptr), void) { +} + +int main() { + return 0; +} diff --git a/test/compile_fail_overrider_return_type.cpp b/test/compile_fail_overrider_return_type.cpp new file mode 100644 index 00000000..1091eea6 --- /dev/null +++ b/test/compile_fail_overrider_return_type.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: overrider return type is not convertible to method return type + +#include + +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(weight, (virtual_ptr), int); + +// Without the check, this fails on the return statement of the thunk that +// calls the overrider. +BOOST_OPENMETHOD_OVERRIDE(weight, (virtual_ptr), std::string) { + return "heavy"; +} + +int main() { + return 0; +} diff --git a/test/compile_fail_virtual_ptr_by_lvalue_reference.cpp b/test/compile_fail_virtual_ptr_by_lvalue_reference.cpp new file mode 100644 index 00000000..60c7dae7 --- /dev/null +++ b/test/compile_fail_virtual_ptr_by_lvalue_reference.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: virtual_ptr<> must be passed by value or by const reference + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +// A non-const reference cannot bind to the `virtual_ptr` of a derived class, +// and the dispatch would take `virtual_ptr` itself for the class of the object. +BOOST_OPENMETHOD(poke, (virtual_ptr&), void); + +int main() { + initialize(); + Dog dog; + virtual_ptr p(dog); + poke(p); + + return 0; +} diff --git a/test/compile_fail_virtual_ptr_by_rvalue_reference.cpp b/test/compile_fail_virtual_ptr_by_rvalue_reference.cpp new file mode 100644 index 00000000..d55fc881 --- /dev/null +++ b/test/compile_fail_virtual_ptr_by_rvalue_reference.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: virtual_ptr<> must be passed by value or by const reference + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +// By rvalue reference, a `virtual_ptr` is not a virtual parameter. +BOOST_OPENMETHOD(poke, (virtual_ptr&&), void); + +int main() { + initialize(); + Dog dog; + poke(virtual_ptr(dog)); + + return 0; +} diff --git a/test/compile_fail_virtual_ptr_to_pointer.cpp b/test/compile_fail_virtual_ptr_to_pointer.cpp new file mode 100644 index 00000000..29b07213 --- /dev/null +++ b/test/compile_fail_virtual_ptr_to_pointer.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: virtual_ptr<> takes a class or a smart pointer + +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() = default; +}; + +BOOST_OPENMETHOD_CLASSES(Animal); + +// `virtual_ptr` is itself a pointer: the parameter is `virtual_ptr`. +BOOST_OPENMETHOD(poke, (virtual_ptr), void); + +int main() { + Animal animal; + poke(&animal); + + return 0; +}