better compile-time error messages

This commit is contained in:
Jean-Louis Leroy
2025-09-20 10:44:59 -04:00
parent 1c0e69b94c
commit 7d5557fb18
12 changed files with 189 additions and 95 deletions

View File

@@ -480,6 +480,10 @@ namespace detail {
template<class Class, class Registry>
struct is_virtual<virtual_ptr<Class, Registry, void>> : std::true_type {};
template<class Class, class Registry>
struct is_virtual<virtual_ptr<Class, Registry, void>&> : std::true_type {
};
template<class Class, class Registry>
struct is_virtual<const virtual_ptr<Class, Registry, void>&> : std::true_type {
};
@@ -1920,27 +1924,6 @@ struct has_static_offsets<
Method, std::void_t<decltype(static_offsets<Method>::slots)>>
: std::true_type {};
template<class Registry, class Parameter>
struct using_same_registry : std::true_type {};
template<class Registry, typename Type, class OtherRegistry>
struct using_same_registry<Registry, virtual_ptr<Type, OtherRegistry>>
: std::is_same<Registry, OtherRegistry> {};
template<class Registry, typename Type, class OtherRegistry>
struct using_same_registry<Registry, const virtual_ptr<Type, OtherRegistry>&>
: std::is_same<Registry, OtherRegistry> {};
template<typename, class>
struct valid_method_parameter : std::true_type {};
template<typename T, class Registry>
struct valid_method_parameter<virtual_<T>, Registry>
: std::bool_constant<
has_vptr_fn<virtual_type<T, Registry>, Registry> ||
Registry::rtti::template is_polymorphic<virtual_type<T, Registry>>> {
};
template<class Registry>
using method_base = std::conditional_t<
Registry::has_deferred_static_rtti, deferred_method_info, method_info>;
@@ -1968,6 +1951,40 @@ template<class Class, class Registry>
struct parameter_traits<const virtual_ptr<Class, Registry, void>&, Registry>
: virtual_traits<const virtual_ptr<Class, Registry, void>&, Registry> {};
template<typename...>
constexpr bool false_t = false; // workaround before CWG2518/P2593R1
template<typename T, class Registry, typename = void>
struct check_method_parameter : std::true_type {};
template<typename T, class Registry, typename U>
struct check_method_parameter<virtual_<T>, Registry, U> : std::false_type {
static_assert(false_t<T>, "virtual_traits not specialized for type");
};
template<typename T, class Registry>
struct check_method_parameter<
virtual_<T>, Registry,
std::void_t<typename virtual_traits<T, Registry>::virtual_type>>
: std::bool_constant<
has_vptr_fn<virtual_type<T, Registry>, Registry> ||
Registry::rtti::template is_polymorphic<virtual_type<T, Registry>>> {
static_assert(
check_method_parameter::value,
"virtual_<> parameter is not a polymorphic class and no "
"boost_openmethod_vptr is applicable");
};
template<class Class, class Registry>
struct check_method_parameter<virtual_ptr<Class, Registry>, Registry, void>
: std::true_type {};
template<class Class, class Registry, class MethodRegistry>
struct check_method_parameter<
virtual_ptr<Class, Registry>, MethodRegistry, void> : std::false_type {
static_assert(
false_t<Class, Registry, MethodRegistry>, "registry mismatch");
};
} // namespace detail
//! Implement a method
@@ -2182,14 +2199,9 @@ class method<Id, ReturnType(Parameters...), Registry>
mp11::mp_list<Parameters...>, detail::is_virtual>::value;
// sanity checks
static_assert(
(detail::check_method_parameter<Parameters, Registry>::value && ...));
static_assert(Arity > 0, "method has no virtual parameters");
static_assert(
(... && detail::using_same_registry<Registry, Parameters>::value),
"method and parameters use different registries");
static_assert(
(... && detail::valid_method_parameter<Parameters, Registry>::value),
"virtual_<> parameter is not polymorphic and no boost_openmethod_vptr "
"function is available");
type_id vp_type_ids[Arity];
@@ -2599,38 +2611,74 @@ method<Id, ReturnType(Parameters...), Registry>::fn_ambiguous(
}
// -----------------------------------------------------------------------------
// thunk
// overriders
namespace detail {
template<class T, class U>
struct compatible_parameters : std::is_same<T, U> {
template<typename T, typename U>
struct same_reference_category {
static constexpr bool value = (std::is_lvalue_reference<T>::value ==
std::is_lvalue_reference<U>::value) &&
(std::is_rvalue_reference<T>::value ==
std::is_rvalue_reference<U>::value);
};
template<class T1, class T2, typename = void>
struct check_overrider_parameter : std::false_type {
static_assert(
compatible_parameters::value,
"non-virtual parameters must have the same type");
false_t<T1, T2>, "non-virtual parameter types must match exactly");
};
template<class T, class U>
struct compatible_parameters<virtual_<T>, U> : std::true_type {};
template<class T, class U>
struct compatible_parameters<virtual_<T>, virtual_<U>> : std::false_type {
template<class T1, class T2>
struct check_overrider_parameter<
T1, T2,
std::enable_if_t<
is_virtual_ptr<T1> && is_virtual_ptr<T2> &&
!same_reference_category<T1, T2>::value>> : std::false_type {
static_assert(
false, "virtual_<> is not allowed in overrider parameter lists");
false_t<T1, T2>, "different virtual_ptr<> reference categories");
};
template<class T, class U, class Registry>
struct compatible_parameters<virtual_ptr<T, Registry>, virtual_ptr<U, Registry>>
template<class T1, class T2>
struct check_overrider_parameter<
T1, T2, std::enable_if_t<is_virtual_ptr<T1> && !is_virtual_ptr<T2>>>
: std::false_type {
static_assert(
false_t<T1, T2>,
"virtual_ptr<> is required in overrider in same position as in "
"method");
};
template<class T>
struct check_overrider_parameter<T, T, void> : std::true_type {};
template<class T1, class T2>
struct check_overrider_parameter<virtual_<T1>, T2, void> : std::true_type {};
template<class T1, class T2>
struct check_overrider_parameter<virtual_<T1>, virtual_<T2>, void>
: std::false_type {
static_assert(false_t<T1, T2>, "virtual_<> is not allowed in overriders");
};
template<class T, class R>
struct check_overrider_parameter<virtual_ptr<T, R>, virtual_ptr<T, R>, void>
: std::true_type {};
template<class T, class U, class Registry>
struct compatible_parameters<
const virtual_ptr<T, Registry>&, const virtual_ptr<U, Registry>&>
: std::true_type {};
template<class T1, class R1, class T2, class R2>
struct check_overrider_parameter<virtual_ptr<T1, R1>, virtual_ptr<T2, R2>, void>
: std::is_same<R1, R2> {
static_assert(check_overrider_parameter::value, "registry mismatch");
};
template<class T, class U, class Registry>
struct compatible_parameters<
virtual_ptr<T, Registry>&&, virtual_ptr<U, Registry>&&> : std::true_type {};
template<class T1, class R1, class T2, class R2>
struct check_overrider_parameter<
const virtual_ptr<T1, R1>&, const virtual_ptr<T2, R2>&, void>
: check_overrider_parameter<virtual_ptr<T1, R1>, virtual_ptr<T2, R2>> {};
template<class T1, class R1, class T2, class R2>
struct check_overrider_parameter<
virtual_ptr<T1, R1>&&, virtual_ptr<T2, R2>&&, void>
: check_overrider_parameter<virtual_ptr<T1, R1>, virtual_ptr<T2, R2>> {};
} // namespace detail
@@ -2643,7 +2691,8 @@ auto method<Id, ReturnType(Parameters...), Registry>::
detail::remove_virtual_<Parameters>... arg) -> ReturnType {
using namespace detail;
static_assert(
(compatible_parameters<Parameters, OverriderParameters>::value && ...),
(check_overrider_parameter<Parameters, OverriderParameters>::value &&
...),
"virtual_ptr category mismatch");
return Overrider(
detail::parameter_traits<Parameters, Registry>::template cast<
@@ -2651,9 +2700,6 @@ auto method<Id, ReturnType(Parameters...), Registry>::
std::forward<detail::remove_virtual_<Parameters>>(arg))...);
}
// -----------------------------------------------------------------------------
// overriders
template<
typename Id, typename... Parameters, typename ReturnType, class Registry>
template<auto Function, typename FnReturnType>

View File

@@ -14,7 +14,7 @@ namespace detail {
template<class T>
struct shared_ptr_cast_traits {
static_assert(false, "invalid cast");
static_assert(false_t<T>, "cast only allowed to a shared_ptr");
};
template<class T>
@@ -32,6 +32,23 @@ struct shared_ptr_cast_traits<std::shared_ptr<T>&&> {
using virtual_type = T;
};
template<typename T, class Registry>
struct check_method_parameter<std::shared_ptr<T>&, Registry, void>
: std::false_type {
static_assert(
false_t<T>,
"std::shared_ptr cannot be passed by non-const lvalue reference");
};
template<typename T, class Registry>
struct check_method_parameter<
virtual_ptr<std::shared_ptr<T>, Registry>&, Registry, void>
: std::false_type {
static_assert(
false_t<T>,
"std::shared_ptr cannot be passed by non-const lvalue reference");
};
} // namespace detail
//! Specialize virtual_traits for std::shared_ptr by value.

View File

@@ -28,17 +28,16 @@ struct virtual_traits<std::unique_ptr<Class>, Registry> {
return *arg;
}
//! Cast to another type.
//! Cast to a type.
//!
//! Cast the object pointed to another type, using `static_cast` if
//! possible, and `Registry::rtti::dynamic_cast_ref` otherwise. If the
//! cast succeeds, return a `std::unique_ptr` to the object,
//! transferring ownership to it.
//! Cast a reference to the managed object, using `static_cast` if possible,
//! and `Registry::rtti::dynamic_cast_ref` otherwise. If the cast succeeds,
//! transfer ownership to a `std::unique_ptr` to the target type, and return
//! it.
//!
//! @tparam Derived A lvalue reference type to a `std::unique_ptr`.
//! @param obj A xvalue reference to a `unique_ptr<Class>`.
//! @return A `std::unique_ptr` to the same object, cast to
//! `Derived::element_type`.
//! @tparam Derived A xvalue reference to a `std::unique_ptr`.
//! @param obj A xvalue reference to a `std::unique_ptr`.
//! @return A `std::unique_ptr<Derived::element_type>`.
template<typename Derived>
static auto cast(std::unique_ptr<Class>&& ptr) {
if constexpr (detail::requires_dynamic_cast<Class&, Derived&>) {

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2018-2025 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)
#include <boost/openmethod.hpp>
using boost::openmethod::virtual_;
struct Cat {};
BOOST_OPENMETHOD(poke, (virtual_<Cat&>), void);
int main() {
poke(Cat{});
return 0;
}

View File

@@ -7,5 +7,9 @@
struct Cat {};
Cat felix;
boost::openmethod::virtual_ptr<Cat> p(felix);
int main() {
Cat felix;
boost::openmethod::virtual_ptr<Cat> p(felix);
return 0;
}

View File

@@ -7,6 +7,12 @@
using boost::openmethod::virtual_;
struct Cat {};
struct Cat { virtual ~Cat() {} };
BOOST_OPENMETHOD(poke, (virtual_<Cat>), void);
int main() {
Cat felix;
poke(felix);
return 0;
}

View File

@@ -7,7 +7,13 @@
using namespace boost::openmethod;
struct Cat {};
struct Cat { virtual ~Cat() {} };
BOOST_OPENMETHOD(
poke, (virtual_ptr<Cat, release_registry>), void, debug_registry);
int main() {
Cat felix;
poke(felix);
return 0;
}

View File

@@ -7,10 +7,16 @@
using namespace boost::openmethod;
struct Animal {};
struct Animal { virtual ~Animal() {} };
struct Cat : Animal {};
BOOST_OPENMETHOD(poke, (virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (Cat&), void) {
}
int main() {
Cat felix;
poke(felix);
return 0;
}

View File

@@ -7,10 +7,16 @@
using namespace boost::openmethod;
struct Animal {};
struct Animal { virtual ~Animal() {} };
struct Cat : Animal {};
BOOST_OPENMETHOD(poke, (const virtual_ptr<Animal>&), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr<Cat>), void) {
}
int main() {
Cat felix;
poke(felix);
return 0;
}

View File

@@ -8,10 +8,16 @@
using namespace boost::openmethod;
struct Animal {};
struct Animal { virtual ~Animal() {} };
struct Cat : Animal {};
BOOST_OPENMETHOD(poke, (shared_virtual_ptr<Animal>&), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (shared_virtual_ptr<Cat>&), void) {
}
int main() {
Cat felix;
poke(felix);
return 0;
}

View File

@@ -7,10 +7,16 @@
using namespace boost::openmethod;
struct Animal {};
struct Animal { virtual ~Animal() {} };
struct Cat : Animal {};
BOOST_OPENMETHOD(poke, (virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (const virtual_ptr<Cat>&), void) {
}
int main() {
Cat felix;
poke(felix);
return 0;
}

View File

@@ -129,36 +129,11 @@ static_assert(
struct registry1 : default_registry::with<unique<registry1>> {};
struct registry2 : default_registry::with<unique<registry2>> {};
static_assert(
detail::using_same_registry<registry1, virtual_ptr<a, registry1>>::value);
static_assert(detail::using_same_registry<registry1, int>::value);
static_assert(
!detail::using_same_registry<registry1, virtual_ptr<a, registry2>>::value);
static_assert(valid_method_parameter<default_registry, virtual_<a&>>::value);
static_assert(
valid_method_parameter<default_registry, virtual_<const a&>>::value);
static_assert(valid_method_parameter<default_registry, int>::value);
struct non_polymorphic {};
static_assert(!valid_method_parameter<
virtual_<non_polymorphic&>, default_registry>::value);
struct non_polymorphic_inplace_vptr {};
auto boost_openmethod_vptr(const non_polymorphic_inplace_vptr&, void*)
-> vptr_type;
static_assert(
valid_method_parameter<
virtual_<non_polymorphic_inplace_vptr&>, default_registry>::value);
static_assert(valid_method_parameter<
virtual_<const non_polymorphic_inplace_vptr&>,
default_registry>::value);
// clang-format on
static_assert(std::is_same_v<