English | 中文 · Advanced guide: GUIDE.md | GUIDE.zh-CN.md
Bind C++ classes and functions to Lua with a single header — no code generation, no wrapper boilerplate.
LuaClass<Cat>(L, "Cat")
.ctor<std::string>()
.fun("eat", &Cat::eat)
.get("name", &Cat::name).set("name", &Cat::setName);local c = Cat.new("Bingo")
c:eat{ "fish", "milk" }
c.name = "Bingo the Brave"That's the whole idea. Keep reading for a gentle, top-to-bottom tour.
- Why luaaa
- Requirements
- Install
- Quick start
- Binding member & static functions
- Constructors
- Properties
- Modules & globals
- Inheritance in Lua
- Lua functions as C++ callbacks
- Run the example
- Where to next
- License
- One header. Copy
luaaa.hpp,#includeit, done. No build step, no.cpp, no external tool. - No wrappers. Bind your existing classes and functions directly — you don't rewrite them.
- Small surface. Three names do almost everything:
LuaClass,LuaModule, and (for custom types)LuaStack. - Automatic conversions. Numbers, strings,
std::string, and every standard container flow between C++ and Lua for you. (const char*args are borrowed from Lua and valid only for the call — usestd::stringto keep a string; see Guide → String lifetime.) - Portable. Works with Lua 5.1 – 5.5 and LuaJIT, and has an embedded mode with no C++ standard library.
- A C++11 compiler (C++14 or newer unlocks a faster
std::tuplepath; still optional). - Lua 5.1, 5.2, 5.3, 5.4, 5.5, or LuaJIT — headers and library available to your build.
luaaa is header-only. Drop luaaa.hpp into your project and include it:
#include "luaaa.hpp"
using namespace luaaa; // optional, but the examples assume itIt needs nothing but Lua and the C++ standard library.
Say you already have this plain C++ class — it knows nothing about Lua:
class Cat {
public:
explicit Cat(const std::string& name) : m_name(name), m_age(1) {}
void eat(const std::list<std::string>& foods); // takes a list
const std::string& name() const;
void setName(const std::string& n);
private:
std::string m_name;
int m_age;
};Bind it to an already-created lua_State* L:
#include "luaaa.hpp"
using namespace luaaa;
void bind(lua_State* L) {
LuaClass<Cat>(L, "Cat") // expose C++ `Cat` to Lua as "Cat"
.ctor<std::string>() // a constructor taking a string
.fun("eat", &Cat::eat) // a method
.get("name", &Cat::name) // a readable property...
.set("name", &Cat::setName); // ...that is also writable
}Now Lua can use it — the array passed to eat becomes a std::list<std::string> automatically:
local c = Cat.new("Bingo") -- calls the C++ constructor
c:eat{ "fish", "milk" } -- Lua table -> std::list<std::string>
print(c.name) --> Bingo
c.name = "Bingo the Brave" -- calls setName()The object's lifetime is managed by Lua's garbage collector: when c is collected, Cat's destructor runs.
fun binds anything callable: member functions, static members, free functions, lambdas.
class Cat {
public:
void eat(const std::list<std::string>&); // member
static void meow(const std::string& who); // static
std::string toString() const; // for __tostring
};
LuaClass<Cat>(L, "Cat")
.ctor<std::string>()
.fun("eat", &Cat::eat)
.fun("meow", &Cat::meow) // a static member, bound like any method
.fun("__tostring", &Cat::toString) // Lua metamethods work too
.fun("play", [](int minutes) { // a lambda is fine
printf("played for %d min\n", minutes);
});local c = Cat.new("Bingo")
c:eat{ "fish" }
c:meow("Bingo") -- static members are called on an instance, too
print(c) -- __tostring: prints "Bingo (1y)"
c:play(10)Note: a
static/free function bound withfunis called like a method —c:meow("Bingo"). The instance in front of the colon is passed as a hiddenselfand skipped; the remaining arguments map to the function's parameters.
Every class needs at least one constructor. The template arguments are the C++ constructor's parameter types; the string names it on the Lua side (default "new").
LuaClass<Cat>(L, "Cat")
.ctor() // Cat.new() -> new Cat()
.ctor<std::string>("create"); // Cat.create("Tom") -> new Cat("Tom")local a = Cat.new()
local b = Cat.create("Tom")You can register several constructors under different names. Factory functions, singletons, and custom deleters are covered in the advanced guide.
get and set turn C++ accessors into Lua fields, so Lua reads and writes them with plain .field syntax.
LuaClass<Cat>(L, "Cat")
.ctor<std::string>()
.get("name", &Cat::name).set("name", &Cat::setName) // read + write
.get("age", &Cat::age ).set("age", &Cat::setAge);local c = Cat.new("Bingo")
print(c.name) -- calls name()
c.age = 3 -- calls setAge(3)- Only a getter → read-only (writing raises a Lua error).
- Only a setter → write-only (reading raises a Lua error).
- Both → read/write.
Getters and setters can also be free functions or lambdas (with or without a Cat& first parameter). See Properties in depth.
A LuaModule groups free functions and constants under one Lua table — handy for things that aren't tied to an object.
void adopt(const std::string& name, std::function<void(const std::string&)> onDone);
LuaModule(L, "shelter")
.def("city", "Catville") // a constant
.def("capacity", 50)
.fun("adopt", adopt); // a free functionprint(shelter.city) --> Catville
shelter.adopt("Bingo", function(who) print(who .. " adopted!") end)Give the module no name (or "_G") to put things straight into Lua's globals:
LuaModule(L).def("pi", 3.14159); // global `pi`As soon as you bind any class, luaaa injects two helpers into that lua_State — no glue to paste:
luaaa:extend(Base, fields)— make a subclass of an exported class (fieldsoptional).luaaa:base(obj)— get the underlying C++ object of an instance (to call an overridden method).
local SpecialCat = luaaa:extend(Cat, { tricks = 0 })
function SpecialCat:learn() self.tricks = self.tricks + 1 end
function SpecialCat:meow(who) -- override...
print(self.name .. " purrs first")
luaaa:base(self):meow(who) -- ...then call the C++ method
end
local felix = SpecialCat:new("Felix")
felix:learn()
felix:meow("Felix")A C++ function can accept a Lua function. Just declare the parameter as a std::function (preferred) or a plain function pointer:
void onEach(std::function<int(int)> cb); // preferred
void onEvent(int (*cb)(const char*)); // raw pointer (limited)onEach(function(x) return x * 2 end)Prefer the std::function form: it owns its own reference to the Lua function, so it can be stored, copied, called many times, and re-entered. The differences and the caveats of the raw-pointer form are explained in Callbacks in depth.
The example/ folder contains a complete, runnable story — The Little Cat Shelter — plus an embedded (no-stdlib) variant, The Feeder Node.
bash example/build.sh # build & run both
bash example/build.sh desktop # only the full std-lib example
bash example/build.sh embedded # only the no-stdlib exampleThe script auto-detects Lua via pkg-config. If it can't find your Lua, point it at the right paths:
LUA_CFLAGS=-I/usr/include/lua5.4 LUA_LIBS=-llua5.4 bash example/build.shOr compile by hand:
cd example
c++ -std=c++11 example.cpp -I/usr/include/lua5.4 -llua5.4 -lm -o example && ./exampleYou've seen everything you need for day-to-day use. The Advanced Guide covers the rest, as a reference and FAQ:
- Automatic type conversions and the full container/
pair/tuplesupport table - Teaching luaaa your own types with
LuaStack - Constructor variants: factories, singletons, custom deleters, name conflicts
- Overloaded functions and disambiguation
- Callbacks in depth (
std::functionvs raw pointers) - Metamethods (
__index,__newindex,__gc,__tostring) - Multiple
lua_States - Embedded / no-stdlib builds for microcontrollers
- Feature macros, GC ownership rules, and troubleshooting
MIT. See LICENSE.