Skip to content

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:

  1. Weld the type — add [[=welder::weld(...)]] and bind it, if it's yours.
  2. Vouch for it — a trust_bindable mark or a self-contained rod caster, if it's registered somewhere welder can't see.
  3. mark::exclude the 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.