Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 102 additions & 6 deletions include/boost/openmethod/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,14 @@ namespace detail {
template<class...>
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<class T, typename = void>
constexpr bool is_complete = false;

template<class T>
constexpr bool is_complete<T, std::void_t<decltype(sizeof(T))>> = true;

template<class Registry, class Class, typename... Bases>
struct use_class_aux<Registry, mp11::mp_list<Class, Bases...>> :
std::conditional_t<
Expand Down Expand Up @@ -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... Classes>
class use_classes {
static_assert(
(detail::is_complete<Classes> && ...),
"use_classes: a class is incomplete");

detail::use_classes_tuple_type<Classes...> tuple;
};

Expand Down Expand Up @@ -1218,6 +1230,10 @@ inline auto final_virtual_ptr(Arg&& obj) {
//! @see [Performance](xref:ROOT:performance.adoc)
template<class Class, class Registry, typename>
class virtual_ptr {
static_assert(
std::is_class_v<Class>,
"virtual_ptr<> takes a class or a smart pointer: virtual_ptr<Node>, "
"not virtual_ptr<Node*>");

using traits = virtual_traits<Class&, Registry>;

Expand Down Expand Up @@ -2389,17 +2405,24 @@ struct validate_method_parameter<
false_t<Class, Registry, MethodRegistry>, "registry mismatch");
};

template<class Class, class Registry>
// 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<class Class, class Registry, class MethodRegistry>
struct validate_method_parameter<
virtual_ptr<Class, Registry>&, Registry, void> : std::true_type {
static_assert(check_affinity<Class>::value);
virtual_ptr<Class, Registry>&, MethodRegistry, void> : std::false_type {
static_assert(
false_t<Class, Registry, MethodRegistry>,
"virtual_ptr<> must be passed by value or by const reference");
};

template<class Class, class Registry, class MethodRegistry>
struct validate_method_parameter<
virtual_ptr<Class, Registry>&, MethodRegistry, void> : std::false_type {
virtual_ptr<Class, Registry>&&, MethodRegistry, void> : std::false_type {
static_assert(
false_t<Class, Registry, MethodRegistry>, "registry mismatch");
false_t<Class, Registry, MethodRegistry>,
"virtual_ptr<> must be passed by value or by const reference");
};

template<class Class, class Registry>
Expand Down Expand Up @@ -3138,9 +3161,55 @@ struct validate_overrider_parameter<
template<class T>
struct validate_overrider_parameter<T, T, void> : 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<class P, class Q>
constexpr bool discards_const = std::is_const_v<P> && !std::is_const_v<Q>;

template<class T>
using designated_class = std::remove_pointer_t<std::remove_reference_t<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<class T1, class R1, class T2, class R2>
constexpr auto same_pointer_kind() -> bool {
if constexpr (IsSmartPtr<T1, R1> != IsSmartPtr<T2, R2>) {
return false;
} else if constexpr (!IsSmartPtr<T1, R1>) {
return true;
} else {
return std::is_same_v<
typename virtual_traits<T1, R1>::template rebind<
typename T2::element_type>,
T2>;
}
}

// Whether an overrider returning `From` can implement a method returning `To`.
template<typename From, typename To>
constexpr bool returns_compatible =
std::is_void_v<To> ? std::is_void_v<From> : std::is_convertible_v<From, To>;

template<class T1, class R1, class T2>
struct validate_overrider_parameter<virtual_<T1, R1>, T2, void> :
std::true_type {};
std::true_type {
using P = designated_class<T1>;
using Q = designated_class<T2>;

// Only between classes of the same hierarchy: an interop parameter such as
// `virtual_<const std::any&>` rejects a mutable overrider parameter itself.
static_assert(
!std::conjunction_v<
std::bool_constant<discards_const<P, Q>>,
std::is_base_of<std::remove_cv_t<P>, std::remove_cv_t<Q>>>,
"overrider parameter discards const qualifier");
};

template<class T1, class R1, class T2, class R2>
struct validate_overrider_parameter<virtual_<T1, R1>, virtual_<T2, R2>, void> :
Expand All @@ -3156,6 +3225,14 @@ template<class T1, class R, class T2, class R2>
struct validate_overrider_parameter<
virtual_ptr<T1, R>, virtual_ptr<T2, R2>, void> : std::true_type {
static_assert(std::is_same_v<R, R2>, "registry mismatch");
static_assert(
same_pointer_kind<T1, R, T2, R2>(),
"different virtual_ptr<> pointer kinds");
static_assert(
!discards_const<
typename virtual_ptr<T1, R>::element_type,
typename virtual_ptr<T2, R2>::element_type>,
"overrider parameter discards const qualifier");
using C1 = virtual_type<virtual_ptr<T1, R>, R>;
using C2 = virtual_type<virtual_ptr<T2, R>, R>;
static_assert(
Expand All @@ -3170,6 +3247,14 @@ struct validate_overrider_parameter<
const virtual_ptr<T1, R>&, const virtual_ptr<T2, R2>&, void> :
std::true_type {
static_assert(std::is_same_v<R, R2>, "registry mismatch");
static_assert(
same_pointer_kind<T1, R, T2, R2>(),
"different virtual_ptr<> pointer kinds");
static_assert(
!discards_const<
typename virtual_ptr<T1, R>::element_type,
typename virtual_ptr<T2, R2>::element_type>,
"overrider parameter discards const qualifier");
using C1 = virtual_type<const virtual_ptr<T1, R>&, R>;
using C2 = virtual_type<const virtual_ptr<T2, R>&, R>;
static_assert(
Expand All @@ -3183,6 +3268,14 @@ template<class T1, class R, class T2, class R2>
struct validate_overrider_parameter<
virtual_ptr<T1, R>&&, virtual_ptr<T2, R2>&&, void> : std::true_type {
static_assert(std::is_same_v<R, R2>, "registry mismatch");
static_assert(
same_pointer_kind<T1, R, T2, R2>(),
"different virtual_ptr<> pointer kinds");
static_assert(
!discards_const<
typename virtual_ptr<T1, R>::element_type,
typename virtual_ptr<T2, R2>::element_type>,
"overrider parameter discards const qualifier");
using C1 = virtual_type<virtual_ptr<T1, R>&&, R>;
using C2 = virtual_type<virtual_ptr<T2, R>&&, R>;
static_assert(
Expand Down Expand Up @@ -3272,6 +3365,9 @@ auto method<Id, ReturnType(Parameters...), Registry>::
(validate_overrider_parameter<Parameters, OverriderParameters>::value &&
...),
"virtual_ptr category mismatch");
static_assert(
returns_compatible<OverriderReturn, ReturnType>,
"overrider return type is not convertible to method return type");

return Overrider(
detail::parameter_traits<Parameters, Registry>::template cast<
Expand Down
23 changes: 23 additions & 0 deletions test/compile_fail_classes_incomplete.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/openmethod.hpp>

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;
}
31 changes: 31 additions & 0 deletions test/compile_fail_overrider_discards_const_reference.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/openmethod.hpp>

using namespace boost::openmethod;

struct Animal {
virtual ~Animal() = default;
};

struct Dog : Animal {};

BOOST_OPENMETHOD_CLASSES(Animal, Dog);

BOOST_OPENMETHOD(poke, (virtual_<const Animal&>), 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;
}
31 changes: 31 additions & 0 deletions test/compile_fail_overrider_discards_const_virtual_ptr.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/openmethod.hpp>

using namespace boost::openmethod;

struct Animal {
virtual ~Animal() = default;
};

struct Dog : Animal {};

BOOST_OPENMETHOD_CLASSES(Animal, Dog);

BOOST_OPENMETHOD(poke, (virtual_ptr<const Animal>), 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<Dog>), void) {
}

int main() {
return 0;
}
32 changes: 32 additions & 0 deletions test/compile_fail_overrider_pointer_kind.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/openmethod.hpp>
#include <boost/openmethod/interop/std_shared_ptr.hpp>

using namespace boost::openmethod;

struct Animal {
virtual ~Animal() = default;
};

struct Dog : Animal {};

BOOST_OPENMETHOD_CLASSES(Animal, Dog);

BOOST_OPENMETHOD(poke, (virtual_ptr<Animal>), void);

// A `virtual_ptr<Dog>` converts to a `virtual_ptr<Animal>`, 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<Dog>), void) {
}

int main() {
return 0;
}
33 changes: 33 additions & 0 deletions test/compile_fail_overrider_return_type.cpp
Original file line number Diff line number Diff line change
@@ -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 <string>

#include <boost/openmethod.hpp>

using namespace boost::openmethod;

struct Animal {
virtual ~Animal() = default;
};

struct Dog : Animal {};

BOOST_OPENMETHOD_CLASSES(Animal, Dog);

BOOST_OPENMETHOD(weight, (virtual_ptr<Animal>), int);

// Without the check, this fails on the return statement of the thunk that
// calls the overrider.
BOOST_OPENMETHOD_OVERRIDE(weight, (virtual_ptr<Dog>), std::string) {
return "heavy";
}

int main() {
return 0;
}
33 changes: 33 additions & 0 deletions test/compile_fail_virtual_ptr_by_lvalue_reference.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>

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<Animal>&), void);

int main() {
initialize();
Dog dog;
virtual_ptr<Animal> p(dog);
poke(p);

return 0;
}
31 changes: 31 additions & 0 deletions test/compile_fail_virtual_ptr_by_rvalue_reference.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>

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<Animal>&&), void);

int main() {
initialize();
Dog dog;
poke(virtual_ptr<Animal>(dog));

return 0;
}
Loading
Loading