Binding a Primitive Type

March 13, 2026 ยท View on GitHub

This guide shows the smallest useful Luacxx extension point: teaching Luacxx how to push and read a type that behaves like a value.

For a primitive or value-like type, the main tools are:

  • lua::Push<T>
  • lua::Store<T>
  • optionally lua::Get<T>

Example: ColorChannel

Suppose you have a tiny type that wraps an integer:

struct ColorChannel
{
    int value;
};

You can map it to Lua as a number.

Push To Lua

Define Push<ColorChannel> to convert the C++ value into a Lua value:

template <>
struct lua::Push<ColorChannel>
{
    static void push(lua_State* const state, const ColorChannel& source)
    {
        lua_pushinteger(state, source.value);
    }
};

After that, this works:

ColorChannel red{255};
lua::push(state, red);

Store From Lua

Define Store<ColorChannel> to read a Lua value back into an existing C++ object:

template <>
struct lua::Store<ColorChannel>
{
    static void store(ColorChannel& destination, lua_State* const state, const int pos)
    {
        destination.value = lua::get<int>(state, pos);
    }
};

Now you can write:

ColorChannel channel{0};
lua::store(channel, state, 1);

Optional Get<T>

Often you do not need a custom Get<T>. The default lua::get<T>() path can build on Store<T>.

If you want a direct get specialization anyway, it can look like this:

template <>
struct lua::Get<ColorChannel>
{
    static ColorChannel get(lua_State* const state, const int pos)
    {
        ColorChannel result{};
        lua::Store<ColorChannel>::store(result, state, pos);
        return result;
    }
};

Exposing A Function That Uses The Type

Once the conversion exists, bound functions can use the type naturally:

ColorChannel clamp_channel(ColorChannel input)
{
    if (input.value < 0) {
        return {0};
    }
    if (input.value > 255) {
        return {255};
    }
    return input;
}

void open_color(lua_State* const state)
{
    lua::thread env(state);
    env["clamp_channel"] = clamp_channel;
}

Luacxx can then marshal the argument and result through your conversion.

When To Use This Pattern

This is a good fit when:

  • the type has value semantics
  • the Lua representation is simple
  • you do not need userdata identity or methods

Examples include:

  • small enums or enum-like wrappers
  • numeric wrappers
  • tiny structs that naturally map to a number, string, or table

If the type needs methods, identity, or lifetime management, a userdata-backed class binding is usually a better fit.