Troubleshooting & FAQ¶
The errors welder produces are deliberate — a misconfigured toolchain or an unrepresentable type fails loudly at compile time, never as a silent skip. This page maps the messages (and a few runtime surprises) to their fixes; each answer links to the guide page with the full story.
Compiling & toolchain¶
"error: welder requires C++26 reflection…"¶
The guard in <welder/lang.hpp> — the first header any welder TU pulls in — fires
when __cpp_impl_reflection is not defined. welder deliberately does not force
the language level onto your target (welder::headers is the include path only),
so you set the flags yourself:
target_compile_features(my_bindings PRIVATE cxx_std_26) # C++26 …
target_compile_options(my_bindings PRIVATE -freflection) # … + gcc-16's reflection flag
Today only gcc-16 (≥ 16.1) implements the papers welder needs (P2996 + P3394),
so make sure the target compiles with g++-16 and both flags. See
Getting started.
Why can't I import welder;?¶
By design, for now. Only one compiler (gcc-16, experimentally) can build against a
welder module, and on that very compiler -freflection conflicts with a
std-carrying module import mixed with textual std includes — which every
pybind11/Python TU does — producing conflicting imported declaration '__mbstate_t'
errors. So the C++20 module wrapper was removed until the upstream gcc bugs are
fixed and a second toolchain implements P2996; until then,
#include <welder/vocabulary.hpp> is the way in. The full reasoning, with the gcc
bug numbers, is in Header-only for now.
Binding behavior¶
"static assertion failed: welder: cannot bind this C++ type…"¶
The bindability gate found a surface — a data member, parameter,
return type, or namespace variable — whose type the rod cannot convert to a
meaningful target-language value. The offending type is the template argument of
the failing assert_bindable<B, T, L> instantiation, so read it off the
diagnostic backtrace. Three fixes, in order of preference:
- Weld the type — add
[[=welder::weld(...)]]and bind it, if it's yours. - Vouch for it — a
trust_bindablemark or a self-contained rod caster, if it's registered somewhere welder can't see. mark::excludethe member that uses it, if it shouldn't cross the boundary.
Also check your converter headers: std::string, std::vector and friends are
native to pybind11/nanobind only with the converter header included
(<pybind11/stl.h>, <nanobind/stl/string.h>, …) — forget it and the gate
correctly reports the type as unbindable.
I welded a namespace but the module is empty¶
weld is the gate: the default stitch-welding carriage binds only leaf
entities carrying a [[=welder::weld(...)]] marker — a namespace with no welded
types, functions, or variables yields an empty module. If the code is yours, mark
the entities you want bound. If it's a third-party library you can't annotate,
swap in the greedy tack-welding carriage, which ignores the missing markers:
using tack = welder::welder<welder::rods::pybind11::rod<>,
welder::naming::none,
welder::tack_welding_carriage>;
tack::weld_namespace<^^thirdparty>(m);
Tack welding drops the marker requirement, not the bindability gate — see Tack welding: an unmarked library.
My Python subclass's virtual override is never called¶
The class needs a trampoline — a C++ subclass that captures each virtual call
and forwards it to Python. welder refuses to silently bind a virtual type as
non-overridable, so a welded type with overridable virtuals must either register a
trampoline (hand-written with the WELDER_PY_* macros, or generated by the
build-time trampolines rod) or opt out with bind_flat. If you reached for
bind_flat — on the type or on that method — you opted that virtual out of
dispatch: Python sees a plain bound method, and C++ calls never route back into
the override. See Overriding virtual methods from
Python.
nanobind refuses my multiple-inheritance type¶
nb::class_<T, Base> takes a single base, so a multi-base or diamond type
binds under pybind11 (and sol2) but not nanobind — that's the one behavioral gap
between the two Python rods. If your hierarchy needs multiple or virtual welded
bases, pick pybind11; the annotated C++ is identical either way. The full
trade-off table is in the Python rods comparison.
Do doc annotations cost anything at runtime?¶
The annotation itself is a compile-time constant: the text is stored inline as a
welder::detail::fixed_string, and the docstring styles assemble the final text
in plain constexpr code. The only runtime artifact is the finished docstring
the backend stores (e.g. Python's __doc__) — and the Lua rods don't even do
that, since Lua has no runtime docstring slot; there the docs live in the
generated LuaCATS stub. See
Docstrings.
Lua specifics¶
My Lua module segfaults at require¶
Almost certainly a Lua version mismatch: a loadable module resolves lua_*
from the host interpreter and has no cross-minor ABI compatibility, so a module
built against Lua 5.4 headers loaded by a 5.5 interpreter segfaults. The module
must be built against the same Lua minor the host runs — point the build at that
install with -DWELDER_LUA_DIR and pin it with -DWELDER_LUA_VERSION (the
LuaBridge3 rod has its own WELDER_LUABRIDGE_LUA_* knobs). welder hard-errors at
configure time when the found Lua minor doesn't match, so if you're segfaulting,
check which interpreter is actually doing the require. See
Lua (sol2) — building a loadable module.
LuaBridge3 crashes on my virtual-base type¶
LuaBridge3 computes a base's cast offset as plain pointer arithmetic, which a virtual base breaks — so a virtual diamond that binds fine under sol2 does not under LuaBridge3. Non-virtual multiple inheritance works on both Lua rods; if you need virtual bases, use the sol2 rod (Lua ≤ 5.4), and keep LuaBridge3 for newer runtimes (5.5, LuaJIT, Luau). The comparison lives in Lua (LuaBridge3).
Stubs & tooling¶
The LuaCATS stub doesn't type == / < / []¶
A lua-language-server limitation, not a welder one:
LuaCATS ---@operator only names the operators the server models (arithmetic,
bitwise, call/len/concat/unm), so the comparison (==, <, <=) and
subscript ([]) metamethods the runtime binds are omitted from the stub — there
is no ---@operator spelling for them. They work at runtime regardless; the stub
simply can't type them. Details in
Stubs (LuaCATS).
.pyi stub generation fails after the build¶
welder_pybind11_generate_stubs() is a POST_BUILD step that imports the
built extension, so the interpreter you hand it must satisfy two things: it can
actually import the module (ABI match with the Python you built against) and it
has pybind11-stubgen installed. If either fails, so does the step. The nanobind
rod sidesteps the pip dependency entirely — its bundled nanobind_add_stub needs
only the stdlib on Python ≥ 3.11. See .pyi stubs.