Open tr8dr opened 2 years ago
Hi tr8dr, and anyone else who sees this and had the same question.
I just started investigating this library yesterday, so I am certainly not well versed on its internals. I took a look at the unit tests in src/unit_tests/variant/variant_conv_test.cpp
. In the last test case, you can see an example of how you might achieve what you're asking:
TEST_CASE("variant test - register_wrapper_converter_for_base_classes<std::shared_ptr<T>>", "[variant]")
{
variant var = std::make_shared<derived>();
CHECK(var.convert(type::get<std::shared_ptr<base>>()) == false);
type::register_wrapper_converter_for_base_classes<std::shared_ptr<derived>>();
CHECK(var.convert(type::get<std::shared_ptr<base>>()) == true);
CHECK(var.convert(type::get<std::shared_ptr<derived>>()) == true);
type::register_wrapper_converter_for_base_classes<std::shared_ptr<other_derived>>();
// negative test, we need first make a down cast, otherwise the target_type converter cannot be found
CHECK(var.convert(type::get<std::shared_ptr<base>>()) == true);
CHECK(var.convert(type::get<std::shared_ptr<other_derived>>()) == false);
}
The issue with this is that this still requires you know the type up front to supply in the type::get
template argument.
It seems that RTTR can't automatically resolve a variant as a base class, but I did find a very dirty way to get it working. This makes me very sad, but here it is (using spdlog
in my scratch environment):
#include "rttr/registration"
#include "spdlog/spdlog.h"
class Base {
RTTR_ENABLE()
public:
void BaseDummy() { spdlog::info("Called Base::Dummy"); }
};
class Other {
RTTR_ENABLE()
public:
void OtherDummy() { spdlog::info("Called Other::Dummy"); }
};
class TestClass : public Base, public Other {
RTTR_ENABLE(Base, Other)
public:
void SetOtherTestClass(const std::shared_ptr<TestClass>& other) {
spdlog::debug("SetOtherTestClass");
OtherTestClass_ = other;
}
void SetOtherClass(const std::shared_ptr<Other>& other) {
spdlog::debug("SetOtherClass");
Other_ = other;
}
void SetBaseClass(const std::shared_ptr<Base>& other) {
spdlog::debug("SetBaseClass");
Base_ = other;
}
void PrintValues() {
if (Other_) {
Other_->OtherDummy();
}
if (Base_) {
Base_->BaseDummy();
}
if (OtherTestClass_) {
OtherTestClass_->PrintValues();
}
}
private:
std::shared_ptr<TestClass> OtherTestClass_;
std::shared_ptr<Base> Base_;
std::shared_ptr<Other> Other_;
};
RTTR_REGISTRATION {
rttr::registration::class_<TestClass>("TestClass")
.constructor<>()
.method("PrintValues", &TestClass::PrintValues)
.method("SetOtherTestClass", &TestClass::SetOtherTestClass)
.method("GetOtherTestClass", &TestClass::GetOtherTestClass)
.method("SetOtherClass", &TestClass::SetOtherClass)
.method("SetBaseClass", &TestClass::SetBaseClass);
// This is the key to upcast/downcast in the hierarchy:
rttr::type::register_wrapper_converter_for_base_classes<std::shared_ptr<TestClass>>();
};
// Wrapper to print error message when class method doesn't exist.
rttr::method GetMethod(const rttr::type& t, const std::string& name) {
rttr::method m = t.get_method(name);
if (!m.is_valid()) {
spdlog::error("{} is not a valid member function of {}", name, std::string(t.get_name()));
}
return m;
}
int main() {
spdlog::set_level(spdlog::level::debug);
rttr::type test_class_type = rttr::type::get_by_name("TestClass");
rttr::variant test_class = test_class_type.create();
rttr::variant other_test_class = test_class_type.create();
rttr::method PrintValues = GetMethod(test_class_type, "PrintValues");
rttr::method SetOtherTestClass = GetMethod(test_class_type, "SetOtherTestClass");
rttr::method SetOtherClass = GetMethod(test_class_type, "SetOtherClass");
rttr::method SetBaseClass = GetMethod(test_class_type, "SetBaseClass");
rttr::variant result;
result = SetOtherTestClass.invoke(test_class, other_test_class);
if (!result.is_valid()) {
spdlog::error("Failed to set other test class.");
}
const rttr::type& original_type = other_test_class.get_type();
// I'm not proud of this, but this is my quick and dirty way to demonstrate the usage of the library to solve
// our shared problem:
for (const auto& param : SetOtherClass.get_parameter_infos()) {
if (other_test_class.can_convert(param.get_type())) {
other_test_class.convert(param.get_type());
result = SetOtherClass.invoke(test_class, other_test_class);
if (!result.is_valid()) {
spdlog::error("Failed to set other class.");
}
} else {
spdlog::error("Cannot convert {} to {}.", std::string(other_test_class.get_type().get_name()),
std::string(param.get_name()));
}
break;
}
spdlog::debug("After SetOtherClass other_test_class variant is a {}",
std::string(other_test_class.get_type().get_name()));
// Convert back to parent class. This works, but not ideal because requires knowing type.
// other_test_class.convert(rttr::type::get<std::shared_ptr<TestClass>>());
// Convert back to parent class. This works.
other_test_class.convert(original_type);
// Convert back to parent class. This doesn't work.
// other_test_class.convert(rttr::type::get_by_name("TestClass"));
spdlog::debug("After convert other_test_class variant is a {}", std::string(other_test_class.get_type().get_name()));
// Repeating the same awful pattern as above, but for the 'Base' class type.
for (const auto& param : SetBaseClass.get_parameter_infos()) {
if (other_test_class.can_convert(param.get_type())) {
other_test_class.convert(param.get_type());
result = SetBaseClass.invoke(test_class, other_test_class);
if (!result.is_valid()) {
spdlog::error("Failed to set other class.");
}
} else {
spdlog::error("Cannot convert {} to {}.", std::string(other_test_class.get_type().get_name()),
std::string(param.get_name()));
}
break;
}
result = PrintValues.invoke(test_class);
if (!result.is_valid()) {
spdlog::error("Failed to print values.");
}
And here is my output:
[2022-12-13 16:41:56.616] [debug] SetOtherTestClass
[2022-12-13 16:41:56.616] [debug] SetOtherClass
[2022-12-13 16:41:56.616] [debug] After SetOtherClass other_test_class variant is a std::shared_ptr<Other>
[2022-12-13 16:41:56.616] [debug] After convert other_test_class variant is a std::shared_ptr<TestClass>
[2022-12-13 16:41:56.616] [debug] SetBaseClass
[2022-12-13 16:41:56.617] [info] Called Other::Dummy
[2022-12-13 16:41:56.617] [info] Called Base::Dummy
Big kudos to Axel for creating this library!
I have a situation where I want to call a function via rttr reflection where one of the arguments is a pointer to a base class:
method (shared_ptr<Base>, ...)
Elsewhere a number of different subclasses may be created with an implied
shared_ptr<Derived1>
,shared_ptr<Derived2>
and so on. These are presented in an argument vector and invoked as:RTTR does not recognize that the rttr::argument's of
shared_ptr<Derived>
in the argument vector are covariant withshared_ptr<Base>
. I am not in control of how these derived classes are created or presented, so would like to have a way of up-casting these to the base shared_ptr OR alternatively have RTTR recognize the covariance and do this under the covers.Is there a facility to deal with this?