Fundamentals 16 min read

Understanding Property Registration and Runtime Access in a C++ Reflection Framework

The article dissects the Ponder C++ reflection library’s Property subsystem, explaining how registration via ClassBuilder creates property entries, how traits and ValueBinders map members to abstract Property objects, and how runtime Get/Set calls traverse templated binders to provide type‑safe, high‑performance access.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Understanding Property Registration and Runtime Access in a C++ Reflection Framework

This article provides an in‑depth exploration of the Property subsystem within a C++ reflection library (the Ponder library). Compared with simple reflection function implementations, Property involves more complex tag dispatch and intermediate steps, so the article walks through the implementation step by step, using concrete examples.

1. Property example code

//-------------------------------------
//register code
//-------------------------------------
__register_type<Vector3>("Vector3")
    .constructor()
    .constructor<double, double, double>()
    .property("x", &Vector3::x)
    .property("y", &Vector3::y)
    .property("z", &Vector3::z);
//-------------------------------------
//use code
//-------------------------------------
auto* metaClass = __type_of<framework::math::Vector3>();
ASSERT_TRUE(metaClass != nullptr);
auto obj = runtime::CreateWithArgs(*metaClass, Args{1.0, 2.0, 3.0});
ASSERT_TRUE(obj != UserObject::nothing);
const reflection::Property* fieldX = nullptr;
metaClass->TryProperty("x", fieldX);
ASSERT_TRUE(fieldX != nullptr);
double x = fieldX->Get(obj).To
();
ASSERT_DOUBLE_EQ(1.0, x);
fieldX->Set(obj, 2.0);
x = fieldX->Get(obj).to
();
ASSERT_DOUBLE_EQ(2.0, x);

The code is divided into two parts: registration (using __register_type ) and usage (retrieving the MetaClass , accessing the property via TryProperty , and calling Get / Set ).

2. Registration code details

The registration is performed through the ClassBuilder returned by __register_type<T>() . The property(name, accessor) function adds a Property to the class’s property table.

template<typename T>
template<typename F>
ClassBuilder<T>& ClassBuilder<T>::property(IdRef name, F accessor) {
    if (target_->properties_table_.find(name.data()) == target_->properties_table_.end()) {
        return AddProperty(detail::PropertyFactory1<T, F>::Create(name, accessor));
    } else {
        current_type_ = const_cast
(&(target_->GetProperty(name)));
        return *this;
    }
}

A second overload accepts two accessors (getter and setter) and creates the property via PropertyFactory2 .

template<typename T>
template<typename F1, typename F2>
ClassBuilder<T>& ClassBuilder<T>::property(IdRef name, F1 accessor1, F2 accessor2) {
    if (target_->properties_table_.find(name.data()) == target_->properties_table_.end()) {
        return AddProperty(detail::PropertyFactory2<T, F1, F2>::Create(name, accessor1, accessor2));
    } else {
        current_type_ = const_cast
(&(target_->GetProperty(name)));
        return *this;
    }
}

3. Core runtime mechanism – the Property class

All runtime properties inherit from an abstract Property class that provides a uniform interface:

class Property : public Type {
public:
    IdReturn name() const;
    ValueKind kind() const;
    virtual bool IsReadable() const;
    virtual bool IsWritable() const;
    Value Get(const UserObject& object) const;
    void Set(const UserObject& object, const Value& value) const;
    // ... type erasure helpers ...
protected:
    virtual Value GetValue(const UserObject& object) const = 0;
    virtual void SetValue(const UserObject& object, const Value& value) const = 0;
};

The most used methods are Get() and Set() , which delegate to the concrete implementation.

4. Value binders – linking a property to actual C++ members

The ValueBinder template bridges the abstract Property with a concrete member (field or function). It uses traits to decide whether the property is writable.

template
class ValueBinder {
public:
    using ClassType = C;
    using AccessType = typename std::conditional
::type;
    using SetType = typename std::remove_reference
::type;
    using Binding = typename PropTraits::template TBinding
;
    ValueBinder(const Binding& b) : bound_(b) {}
    AccessType Getter(ClassType& c) const { return bound_.Access(c); }
    bool Setter(ClassType& c, SetType v) const {
        if constexpr (PropTraits::kIsWritable) return (bound_.Access(c) = v), true;
        else return false;
    }
    // ... GetValue / SetValue wrappers ...
protected:
    Binding bound_;
};

ValueBinder2 extends ValueBinder by allowing an external function object to perform the set operation.

template
class ValueBinder2 : public ValueBinder
{
    using Base = ValueBinder
;
public:
    template
ValueBinder2(const typename Base::Binding& g, S s) : Base(g), set_(s) {}
    bool Setter(typename Base::ClassType& c, typename Base::SetType v) const { set_(c, v); return true; }
    // ... overload for Value ...
protected:
    std::function
set_;
};

Both binders rely on PropTraits (e.g., TMemberTraits or TFunctionTraits ) which describe the member type, return type, and provide a TBinding that actually accesses the member.

5. Trait extraction – TFunctionTraits and TMemberTraits

These traits decompose a function or member pointer into:

Parameter types

Return type

Dispatch type for std::function

Kind (MemberObject vs Function)

The traits also expose the appropriate ValueBinder (or ValueBinder2 ) to be used by the property implementation.

6. Property factories – creating concrete property objects

PropertyFactory1<T, F>::Create builds a SimplePropertyImpl that wraps a GetSet1 (single accessor) pipeline. PropertyFactory2 does the same with GetSet2 (separate getter and setter).

The creation flow can be visualised as:

Deduce the correct Accessor and PropertyImpl from the class type C and the trait PropTraits using GetSet1 / GetSet2 .

Instantiate the appropriate ValueBinder (or ValueBinder2 ) based on writability.

Construct the concrete implementation ( SimplePropertyImpl , EnumPropertyImpl , etc.).

7. Concrete property implementations

Examples include:

SimplePropertyImpl – handles ordinary member objects.

EnumPropertyImpl , ArrayPropertyImpl , UserPropertyImpl – similar structure, specialised for enums, arrays, or user‑defined types.

template
class SimplePropertyImpl : public SimpleProperty {
public:
    SimplePropertyImpl(IdRef name, A accessor);
protected:
    bool IsReadable() const final { return true; }
    bool IsWritable() const final { return true; }
    Value GetValue(const UserObject& object) const final {
        return Value{accessor_.interface_.Getter(object.get
())};
    }
    void SetValue(const UserObject& object, const Value& value) const final {
        if (!accessor_.interface_.Setter(object.Ref
(), value.to
()))
            PONDER_ERROR(ForbiddenWrite(name()));
    }
private:
    A accessor_; // bridges to the actual C++ member
};

8. Runtime get/set process – call‑stack walk‑through

The article walks through a concrete call stack when retrieving Vector3::x :

// level 1
framework::reflection::detail::SimplePropertyImpl<
    framework::reflection::detail::GetSet1<
        framework::math::Vector3,
        framework::reflection::detail::TMemberTraits
>
>::GetValue(const framework::reflection::UserObject& object);

// level 2
framework::reflection::detail::ValueBinder<
    framework::math::Vector3,
    framework::reflection::detail::TMemberTraits
>::Getter(framework::math::Vector3& c);

// level 3
framework::reflection::detail::TMemberTraits
::
    TBinding
::Access(framework::math::Vector3& c);

Each level corresponds to a template instantiation that ultimately dereferences the member pointer and returns the value.

9. Summary

Through a cascade of template classes— GetSet , AccessTraits , ValueBinder , and concrete PropertyImpl —the framework achieves high‑performance, type‑safe runtime property access. The article also notes that while the current C++17 implementation works, a future rewrite using C++20 concepts could simplify the design.

reflectionruntimeCTemplate Metaprogrammingproperty
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.