Conversion Templates
March 13, 2026 · View on GitHub
Luacxx's core customization model is built around four template structs:
lua::Push<T>lua::Store<T>lua::Get<T>lua::Construct<T>
These are the extension points that teach Luacxx how a C++ type should move between C++ and Lua.
The Big Picture
Most of the user-facing helpers are thin wrappers around these templates:
lua::push(...)dispatches tolua::Push<T>lua::store(...)dispatches tolua::Store<T>lua::get<T>(...)dispatches tolua::Get<T>lua::make<T>(...)dispatches tolua::Construct<T>
So when you customize one of these templates, you are changing how that type behaves across a large portion of Luacxx.
lua::Push<T>
Push<T> defines how a C++ value becomes a Lua value.
Conceptually:
template <class T>
struct Push
{
static void push(lua_State* const state, T value);
};
The default behavior in stack.hpp is userdata-oriented: Luacxx constructs a
userdata value for T.
You specialize Push<T> when a type should map to a different Lua
representation, such as:
- a number
- a string
- a table
- a callable
- a specialized userdata encoding
Examples in the codebase include:
- numeric types in
include/luacxx/convert/numeric.hpp - strings in
include/luacxx/convert/string.hpp - containers in
include/luacxx/convert/vector.hpp
lua::Store<T>
Store<T> defines how a Lua value is written into an existing C++ object.
Conceptually:
template <class T>
struct Store
{
static void store(T& destination, lua_State* const state, const int pos);
};
This is the “read from Lua into C++” path.
You specialize Store<T> when:
- the type has a non-default Lua representation
- the type should be rebuilt from a Lua table
nilshould have special meaning- assignment should replace container contents in a custom way
Examples:
std::optional<T>treatsnilas reset ininclude/luacxx/convert/optional.hppstd::vector<T>rebuilds from a Lua array table ininclude/luacxx/convert/vector.hpp- pointer retrieval uses userdata type checks in
include/luacxx/stack.hpp
lua::Get<T>
Get<T> defines how a Lua value is returned as a C++ value.
Conceptually:
template <class T>
struct Get
{
static T get(lua_State* const state, const int pos);
};
The default Get<T> implementation usually constructs a destination object and
delegates to Store<T>.
That means you often only need to specialize Store<T>, not Get<T>.
You specialize Get<T> when:
- returning by value is more direct than default-construct-and-store
- the type should return a reference-like view
- the default
Store<T>-based path is inefficient or awkward - the type is callable or otherwise has a special retrieval path
Examples:
Get<T&>andGet<const T&>ininclude/luacxx/stack.hppGet<std::tuple<...>>ininclude/luacxx/convert/tuple.hppGet<std::function<...>>ininclude/luacxx/convert/callable.hpp
lua::Construct<T>
Construct<T> defines how Luacxx should create userdata for T.
Conceptually:
template <class T>
struct Construct
{
template <class... Args>
static void construct(lua_State* const state, Args&&... args);
};
This is the low-level construction path that powers lua::make<T>() and the
default userdata-oriented Push<T>.
The built-in Construct<T>:
- allocates userdata storage
- records Luacxx type metadata
- placement-constructs the C++ object
- installs a destructor callback
- attaches the correct metatable
There are also built-in specializations for T* and const T*.
You usually do not need to specialize Construct<T> unless you are
changing how userdata should be created at a very low level.
Most custom types should specialize Push<T>, Store<T>, or Get<T> first.
Which Luacxx APIs Use Them
Here is the practical dispatch map:
lua::push(state, value)useslua::Push<T>::push(...)lua::store(destination, state, pos)useslua::Store<T>::store(...)lua::store(destination, index)also forwards toStore<T>lua::get<T>(state, pos)useslua::Get<T>::get(...)lua::get<T>(index)also usesGet<T>lua::make<T>(state, args...)useslua::Construct<T>::construct(...)
Higher-level helpers also build on these:
lua::call(...)pushes arguments and gets return values through themlua::threadandlua::globalassignments rely onpush/store- container conversions recursively use
pushandstorefor element types
When You Usually Specialize Each One
- Specialize
Push<T>when Lua should see a custom representation. - Specialize
Store<T>when Lua values should rebuild or assign intoT. - Specialize
Get<T>when the defaultStore<T>-based retrieval is not the right fit. - Specialize
Construct<T>only when the default userdata construction model itself should change.
Common Pattern
For many value-like types, this is enough:
template <>
struct lua::Push<MyType>
{
static void push(lua_State* const state, const MyType& source)
{
lua_pushinteger(state, source.value);
}
};
template <>
struct lua::Store<MyType>
{
static void store(MyType& destination, lua_State* const state, const int pos)
{
destination.value = lua::get<int>(state, pos);
}
};
Then all of these start working naturally:
lua::push(state, MyType{42});
MyType value{};
lua::store(value, state, 1);
MyType another = lua::get<MyType>(state, 1);
A Useful Rule Of Thumb
Start at the highest-level customization point that matches your need.
- If you just need a custom Lua representation, start with
Push<T>andStore<T>. - If retrieval semantics are special, add
Get<T>. - If userdata construction itself is special, then consider
Construct<T>.
That usually keeps your specializations simpler and lets the rest of Luacxx reuse them automatically.