Namespaces & modules¶
Binding types one at a time is fine, but welder can also bind a whole namespace — or emit an entire importable module — from a single declaration.
In the cookbook
Recipe 02 — Discovery rules drives a whole module
through WELDER_MODULE, submodules and pruning included; Recipe 01 — One of
everything shows the semi-manual weld_function /
weld_variable route; Recipe 08 — Tack welding
binds an unmarked library greedily.
Binding a namespace¶
welder::welder<Rod>::weld_namespace<^^ns>(m) walks a namespace and binds its
contents in declaration order:
- classes (via
weld_type<T>), - class-template instantiations declared through a namespace-scope alias
(
using IntBox = Box<int>;— the alias is the target-language name; see Binding templates), - free functions (overloads included),
- namespace-scope variables.
weld gates leaf entities only (a class type, a free function, a namespace-scope
variable — namespaces are never welded). The namespace's policy (default
automatic) plus member marks then resolve what actually binds.
namespace geometry {
struct [[=welder::weld(welder::lang::py, welder::lang::lua)]] Point { double x, y; };
[[=welder::weld(welder::lang::py, welder::lang::lua)]]
double distance(const Point& a, const Point& b) { /* … */ }
} // namespace geometry
Namespace variables¶
A namespace-scope variable binds as a module attribute:
- const / constexpr → a value snapshot;
-
otherwise → a live get/set over the C++ global: a read returns the current value and a write flows back to the global. Every runtime rod gives you the same behavior, each through its framework's own mechanism:
Rod Live variables via pybind11 / nanobind a ModuleType__class__swap (module-level property)sol2 a metatable proxy ( __index/__newindex) on the module tableLuaBridge3 the registrar's addProperty(a getter/setter pair)
Nested namespaces¶
A nested namespace resolves under the parent's policy (it has no weld of its
own): automatic recurses unless excluded; opt_in recurses only if included —
which keeps detail / impl namespaces out by giving the parent opt_in. A nested
namespace becomes a submodule when it holds bound content.
Binding a namespace as a submodule¶
weld_namespace<^^ns>(m) welds a namespace's contents into m, flat.
weld_namespace_as_submodule<^^ns>(m) instead first creates a submodule of
m and welds the namespace into that — the manual counterpart of what the
namespace walk above does automatically for nested namespaces, for when the
parent module is otherwise hand-written (or when you want a library's namespace
mounted under a name of your choosing).
The submodule's name defaults to the namespace's styled /
weld_as name; an optional trailing
name is used verbatim and beats both. The call returns the new submodule
handle, so hand-written bindings chain right on:
extern "C" int luaopen_mymod(lua_State* L) {
sol::state_view lua(L);
sol::table m = lua.create_table();
using weld = welder::welder<welder::rods::sol2::rod>;
sol::table sub = weld::weld_namespace_as_submodule<^^geometry>(m);
sub["VERSION"] = "1.0"; // the submodule is a nested table
return sol::stack::push(L, m);
}
Binding a single function or variable¶
Welding a whole namespace is convenient, but the semi-manual route lets you drop
down and bind one hand-picked free function or global directly onto a module handle —
the free-standing analogue of weld_type<T>, for when you want welder to lay down
just that entity and keep the rest of the entry point hand-written:
weld_function<^^fn>(m)binds a single free functionfn.weld_variable<^^var>(m)binds a single global/constantvar.
The entity still needs its [[=welder::weld(...)]] (that is always the gate), and
its signature/type still runs the bindability gate — but you do not
have to weld, or even name, the enclosing namespace.
namespace geometry {
[[=welder::weld(welder::lang::py)]] double distance(const Point& a, const Point& b);
[[=welder::weld(welder::lang::py)]] inline constexpr double kUnit{1.0};
} // namespace geometry
PYBIND11_MODULE(geometry, m) {
using weld = welder::welder<welder::rods::pybind11::rod<>>;
weld::weld_function<^^geometry::distance>(m); // one free function
weld::weld_variable<^^geometry::kUnit>(m); // one constant
// … the rest of the module stays ordinary hand-written pybind11 code …
}
weld_variable follows the same const-vs-mutable rule as namespace
variables above (a value snapshot, or a live property on the
Python rods). Like weld_type, both take an optional trailing name argument used
verbatim — and it takes precedence over any
weld_as on the entity:
weld::weld_function<^^geometry::distance>(m, "dist"); // exposed as m.dist
weld::weld_variable<^^geometry::kUnit>(m, "UNIT"); // exposed as m.UNIT
For an overloaded name, ^^fn on the overload set is ambiguous — reflect a
specific overload. weld_function then welds that overload's participating
same-name siblings along with it, as one group (the same set the namespace walk
would bind), so the call means the same thing on every rod — including the Lua
ones, whose tables hold a single value per name. Shape the group with
per-overload marks; the reflected
overload itself always welds (the explicit call outranks its own marks).
Where the framework has a per-function object, weld_function returns it — the
bound function (m.attr(name)) on the Python rods, the table entry on sol2 —
so it chains like weld_type's class
handle; LuaBridge3's fluent
registrar has none, so there it returns void. weld_namespace_as_submodule
returns the new submodule handle for the same reason: keep hand-binding into
it.
Tack welding: an unmarked library¶
Everything so far needs a weld marker. But sometimes you want to bind a
third-party library that has no welder annotations and that you can't edit. For
that, swap the carriage — the traversal engine welder::welder drives — from the
default stitch-welding carriage (bind where the markers direct) to the
tack-welding carriage, which binds greedily: every reflectable type, free
function and global participates, namespaces are recursed, and every public base is
flattened in — the missing weld markers are simply ignored.
The carriage is welder::welder's third template argument:
namespace thirdparty { // no welder annotations anywhere
struct Vec2 { double x, y; double length() const; };
Vec2 midpoint(const Vec2&, const Vec2&);
inline constexpr int ABI_VERSION{3};
} // namespace thirdparty
using tack = welder::welder<welder::rods::pybind11::rod<>,
welder::naming::none,
welder::tack_welding_carriage>; // ← greedy carriage
PYBIND11_MODULE(thirdparty, m) {
tack::weld_namespace<^^thirdparty>(m); // binds Vec2, midpoint, ABI_VERSION …
}
Bindability is still enforced
Tack welding drops the marker requirement, not the bindability
gate. The gate's registration oracle does adapt: under tack
welding, a class/enum type the greedy pass itself registers (any complete,
non-excluded type — like Vec2 in midpoint's signature above) counts as
registered, so the library's own types may appear in its signatures with no
hatch. Everything else still hard-errors at compile time: a genuinely
unrepresentable type, or a forward-declared (incomplete) one the walk
cannot register. Vouch for a type registered elsewhere with a type-level
trust_bindable<T>, or point the tack at a narrower
sub-namespace. Any mark::exclude that does happen to be present is still
honored, so a partially-annotated header can still prune.
The flip side of the greedy oracle: it can't know which namespaces you tack.
A signature naming a registrable type you never actually weld binds fine but
raises the framework's unregistered-type error at call time. One
pybind11-specific wrinkle on top (a framework property, not welder's):
pybind11 renders docstrings at def time, so a signature referencing a type
declared later in the namespace spells the raw C++ name in
docstrings/.pyi stubs — declare types before the signatures that use them
(C++ mostly forces this anyway).
Both carriages ship as welder::stitch_welding_carriage (the default) and
welder::tack_welding_carriage; a custom traversal is a
welder::carriages::basic_carriage<Resolution> with your own resolution policy.
Subclassing welder::welder
Each weld_* entry point is a one-line forward to the carriage (which owns the
resolution and the gates). To go beyond the stock flow you can either inject a
different carriage (above) or derive from welder::welder<Rod, Style, Carriage>
— being all-static it isn't a runtime base, but a subclass reaches the bound
rod_type / name_style / carriage_type and the entry points to assemble a
bespoke routine (a curated subset of a namespace, welded and hand-written
registrations interleaved) without re-implementing the traversal or the gates.
Binding a whole module¶
WELDER_MODULE(ns, rod) emits the language's C entry symbol
(PyInit_<name> for Python, luaopen_<name> for Lua) and fills the module from the
namespace — no PYBIND11_MODULE, no hand-written luaopen_, no per-type weld_type
calls. The namespace token doubles as the module name, and the namespace doc
becomes the module docstring (where the language has one). Include the rod's
module.hpp (not just its rod.hpp) to pull the macro in.
The rod selector is the rod name (pybind11, nanobind, sol2,
luabridge), not the language. Everything above WELDER_MODULE — the namespace and its annotations
— is identical; only the includes and the selector change:
#include <welder/vocabulary.hpp>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <welder/rods/python/pybind11/module.hpp> // rod + WELDER_MODULE
namespace
[[=welder::doc("A small shapes module built by welder.")]]
shapes {
struct
[[=welder::weld(welder::lang::py, welder::lang::lua),
=welder::doc("An axis-aligned rectangle.")]]
Rect {
double w{0.0}, h{0.0};
Rect() = default;
Rect(double width, double height) : w{width}, h{height} {}
[[=welder::doc("The area of the rectangle.")]]
double area() const { return w * h; }
};
[[=welder::weld(welder::lang::py, welder::lang::lua),
=welder::doc("Scale a length by a factor.")]]
double scale(
[[=welder::doc("the length to scale")]] double length,
[[=welder::doc("the multiplier")]] double factor) {
return length * factor;
}
} // namespace shapes
// One line: emits PyInit_shapes and binds the whole namespace into `module`.
// The trailing block is optional post-glue (the module handle is in scope).
WELDER_MODULE(shapes, pybind11) {
module.attr("VERSION") = "1.0";
}
#include <welder/vocabulary.hpp>
#include <sol/sol.hpp>
#include <welder/rods/lua/sol2/module.hpp> // rod + WELDER_MODULE
// ... namespace shapes { Rect, scale } exactly as in the Python tab ...
// Emits luaopen_shapes and binds the whole namespace into `module`
// (a sol::table here). The doc annotations are ignored at runtime by sol2 —
// their Lua home is the LuaCATS stub.
WELDER_MODULE(shapes, sol2) {
module["VERSION"] = "1.0";
}
Under the hood, WELDER_MODULE wraps
welder::welder<Rod>::weld_module<^^ns>(m, pre, post): a pre hook, then
weld_namespace, then a post hook (your trailing block).
By default that is the plain welder::welder<rod>. An optional third argument
names the exact welder::welder<…> type to drive the weld with instead — the way
to thread a name style (or a custom carriage) through the one-line
module form; commas inside the template-id are fine:
WELDER_MODULE(shapes, pybind11,
welder::welder<welder::rods::pybind11::rod<>,
welder::rods::python::pep8>) {}
One WELDER_MODULE per rod per TU — but several rods can coexist
Two rods that emit the same entry symbol collide — pybind11 and nanobind
both emit PyInit_<name>, so only one Python rod per TU. But a Python and a
Lua WELDER_MODULE emit different symbols (PyInit_shapes vs
luaopen_shapes), so one shared object can carry both. That's the basis for
shipping the same module across rods.
Next: Return policies & lifetimes.