Skip to content

Commit 86cd6bb

Browse files
committed
feat: add MemberPointerTraits to support inherited member pointers
- Introduce MemberPointerTraits to extract class_type, value_type and constness from member pointers, eliminating the need for manual class type deduction - Fix: member pointers inherited from base class now work correctly by using traits-deduced class_type for unwrap, with static assertion to verify the member belongs to the class hierarchy - Support both mutable and const member pointers via isConst flag - Add regression test for derived class binding inherited members
1 parent d68580e commit 86cd6bb

2 files changed

Lines changed: 54 additions & 20 deletions

File tree

src/jspp/binding/TypeConverter.h

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,23 +1140,34 @@ InstanceSetterCallback wrapInstanceSetter(Fn&& fn) {
11401140
}
11411141
}
11421142

1143-
// TODO: ValueTraits support for member pointers
1144-
//
1145-
// prop("r", &Color::r) fails when the member is inherited (e.g. Color inherits
1146-
// from floatN4<Color>). wrapInstanceAccessor deduces C from the binding class
1147-
// (Color) but the member pointer's actual class is the base (floatN4), causing
1148-
// type mismatch in unwrap<const C>.
1149-
//
1150-
// Fix: introduce ValueTraits<Member> analogous to FunctionTraits to extract
1151-
// class_type and value_type from the member pointer itself, eliminating the
1152-
// need for the caller to provide C. wrapInstanceAccessor should accept an
1153-
// arbitrary member pointer and unwrap using the deduced class_type.
1154-
//
1155-
// Workaround for now: use lambda getter/setter instead of member pointers
1156-
// for inherited members.
1157-
template <typename C, bool forceReadonly, typename Ty>
1143+
template <typename T>
1144+
struct MemberPointerTraits;
1145+
template <typename ClassType, typename ValueType>
1146+
struct MemberPointerTraits<ValueType ClassType::*> {
1147+
using class_type = ClassType;
1148+
using value_type = ValueType;
1149+
static constexpr bool isConst = false;
1150+
};
1151+
template <typename ClassType, typename ValueType>
1152+
struct MemberPointerTraits<const ValueType ClassType::*> {
1153+
using class_type = ClassType;
1154+
using value_type = ValueType;
1155+
static constexpr bool isConst = true;
1156+
};
1157+
1158+
1159+
template <typename C, bool forceReadonly, typename MemberPtr>
11581160
std::pair<InstanceGetterCallback, InstanceSetterCallback>
1159-
wrapInstanceAccessor(Ty C::* member, ReturnValuePolicy policy) {
1161+
wrapInstanceAccessor(MemberPtr member, ReturnValuePolicy policy) {
1162+
using Traits = MemberPointerTraits<std::remove_cvref_t<MemberPtr>>;
1163+
using value_type = typename Traits::value_type;
1164+
using class_type = typename Traits::class_type;
1165+
1166+
static_assert(
1167+
std::is_base_of_v<class_type, C> || std::is_same_v<class_type, C>,
1168+
"Member pointer does not belong to the bound class hierarchy"
1169+
);
1170+
11601171
InstanceGetterCallback getter = [member,
11611172
policy](InstancePayload& payload, Arguments const& arguments) -> Local<Value> {
11621173
auto inst = payload.unwrap<const C>();
@@ -1168,11 +1179,10 @@ wrapInstanceAccessor(Ty C::* member, ReturnValuePolicy policy) {
11681179
);
11691180
};
11701181
InstanceSetterCallback setter = nullptr;
1171-
if constexpr (!std::is_const_v<std::remove_cvref_t<Ty>> && !forceReadonly) {
1182+
if constexpr (!Traits::isConst && !forceReadonly) {
11721183
setter = [member](InstancePayload& payload, Arguments const& arguments) {
1173-
auto inst = payload.unwrap<C>();
1174-
using value_type = std::remove_cvref_t<Ty>;
1175-
inst->*member = toCpp<value_type>(arguments[0]);
1184+
auto inst = payload.unwrap<C>();
1185+
inst->*member = toCpp<value_type>(arguments[0]);
11761186
};
11771187
}
11781188
return {std::move(getter), std::move(setter)};

test/bugs_test.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,28 @@ TEST_CASE_METHOD(
7575
}
7676

7777

78+
struct Base {
79+
int foo;
80+
int const bar = 42;
81+
Base(int foo) : foo(foo) {}
82+
};
83+
struct Derived : public Base {
84+
using Base::Base;
85+
};
86+
TEST_CASE_METHOD(
87+
BugTestFixture,
88+
"Bug: member pointer from derived class fails unwrap due to base class type mismatch",
89+
"[bugs]"
90+
) {
91+
static auto Test =
92+
binding::defClass<Derived>("Derived").ctor<int>().prop("foo", &Derived::foo).prop("bar", &Base::bar).build();
93+
94+
EngineScope lock{*engine};
95+
engine->registerClass(Test);
96+
97+
REQUIRE_NOTHROW(engine->evalScript(String::newString("new Derived(1).foo = 42")));
98+
REQUIRE_THROWS(engine->evalScript(String::newString("new Derived(1).bar = 42")));
99+
}
100+
101+
78102
} // namespace

0 commit comments

Comments
 (0)