Understanding Function Type Erasure and Runtime Invocation in C++ Reflection (Ponder Library)
The article explains how Ponder’s C++ reflection library uses template‑based type erasure to register and invoke functions at runtime, detailing the Function class, registration syntax, argument conversion, Lua integration, performance considerations, and a complete Vector3 DotProduct example.
This article delves into the Function part of the Ponder C++ reflection library, explaining how to use templates to achieve type erasure for C++ functions and how to invoke these erased functions at runtime. It also discusses the performance trade‑offs of type erasure and provides a concrete example using a Lua function wrapper.
Example code shows a simple Vector3 class with a DotProduct method, registration of the class and its function, and runtime usage of the reflected function:
//-------------------------------------
//declaration
//-------------------------------------
class Vector3 {
public:
double x;
double y;
double z;
public:
Vector3() : x(0.0), y(0.0), z(0.0) {}
Vector3(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {}
double DotProduct(const Vector3& vec) const;
};
//-------------------------------------
//register code
//-------------------------------------
__register_type
("Vector3")
.constructor()
.constructor
()
.function("DotProduct", &Vector3::DotProduct);
//-------------------------------------
//use code
//-------------------------------------
auto* metaClass = __type_of
();
ASSERT_TRUE(metaClass != nullptr);
auto obj = runtime::CreateWithArgs(*metaClass, Args{1.0, 2.0, 3.0});
ASSERT_TRUE(obj != UserObject::nothing);
const reflection::Function* dotProductFunc = nullptr;
metaClass->TryGetFunction("DotProduct", dotProductFunc);
ASSERT_TRUE(dotProductFunc != nullptr);
math::Vector3 otherVec(1.0, 2.0, 3.0);
auto dotRet = runtime::Call(*dotProductFunc, obj, otherVec);
ASSERT_DOUBLE_EQ(dotRet.to
(), 14.0);The registration uses __register_type<T>() to obtain a ClassBuilder and the function(name, func) method to register the member function.
__register_type
("Vector3").function("DotProduct", &Vector3::DotProduct);At runtime the reflected Function object is obtained via TryGetFunction and invoked through runtime::Call , which internally converts arguments to a uniform Args container and returns a Value .
The article then outlines the overall structure of the tutorial, which mirrors the previous Property article, covering:
Basic knowledge
Runtime representation of functions – the Function class
Registration of reflected functions
Implementation of Lua‑side reflection functions
FunctionTraits and FunctionKind
The FunctionKind enum class categorises different callable entities:
/**
* \brief Enumeration of the kinds of function recognised
*/
enum class FunctionKind {
kNone,
kFunction,
kMemberFunction,
kFunctionWrapper,
kBindExpression,
kLambda
};The Function base class provides a virtual interface for runtime invocation, exposing methods such as name() , class_name() , kind() , GetParamCount() , GetParamType() , and a pure virtual Execute(const Args&) method. The C++ version returns a Value , while the Lua version provides CallStraight(lua_State*) .
class Function : public Type {
public:
inline IdReturn name() const { return name_; }
inline IdReturn class_name() const { return class_name_; }
FunctionKind kind() const { return kind_; }
virtual size_t GetParamCount() const = 0;
virtual ValueKind GetParamType(size_t index) const = 0;
virtual std::string_view GetParamTypeName(size_t index) const = 0;
virtual TypeId GetParamTypeIndex(size_t index) const = 0;
virtual TypeId GetParamBaseTypeIndex(size_t index) const = 0;
virtual TypeId GetReturnTypeIndex() const = 0;
virtual TypeId GetReturnBaseTypeIndex() const = 0;
virtual bool ArgsMatch(const Args& arg) const = 0;
virtual Value Execute(const Args& args) const = 0;
};For C++ calls the implementation uses FunctionCaller and FunctionCallerImpl which store a std::function created by FunctionWrapper . The wrapper converts the uniform Args to the concrete parameter types using ConvertArgs and then calls the original function.
class FunctionCaller {
public:
FunctionCaller(const IdRef name) : m_name(name) {}
virtual ~FunctionCaller() {}
virtual Value execute(const Args& args) const = 0;
private:
const IdRef m_name;
};
template
class FunctionCallerImpl : public FunctionCaller {
public:
FunctionCallerImpl(IdRef name, F function)
: FunctionCaller(name), m_function(function) {}
private:
typename FunctionWrapper
::Type m_function;
Value execute(const Args& args) const final {
return DispatchType::call(m_function, args);
}
};The ConvertArgs template extracts each argument from the Args container, handling primitive types as well as user‑defined objects via ConvertArg . The ChooseCallReturner template decides how to wrap the return value based on policies such as policy::ReturnCopy or policy::ReturnInternalRef .
The Lua side mirrors the C++ design but adapts the caller to the Lua C‑function signature ( int (*)(lua_State*) ). The FunctionCaller stores a pointer to the Lua C‑function and provides pushFunction(lua_State*) to push a closure that carries the caller object as a light userdata up‑value.
class FunctionCaller {
public:
FunctionCaller(const IdRef name, int (*fn)(lua_State*) = nullptr)
: m_name(name), m_luaFunc(fn) {}
void pushFunction(lua_State* L) {
lua_pushlightuserdata(L, (void*)this);
lua_pushcclosure(L, m_luaFunc, 1);
}
private:
const IdRef m_name;
int (*m_luaFunc)(lua_State*);
};The Lua FunctionWrapper and CallHelper use LuaValueReader to convert Lua stack values to C++ types and LuaValueWriter to push results back onto the stack.
template
struct FunctionWrapper {
using Type = std::function
;
template
static int call(F func, lua_State* L) {
using ArgEnumerator = std::make_index_sequence
;
return CallHelper
::call
(func, L, ArgEnumerator());
}
};Finally, the article presents runtime analysis of both C++ and Lua function calls, showing call‑stack diagrams and the concrete template instantiations that the compiler generates for a sample Vector3::DotProduct invocation.
Overall, the tutorial provides a comprehensive guide to implementing function type erasure, registration, and runtime invocation in both native C++ and Lua environments using the Ponder reflection framework.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.