Naming conventions¶
Your C++ follows one house style; the language you bind to usually wants another.
Idiomatic C++ has processFile, HTTPServer, maxRetries; PEP 8 asks a
Python caller for process_file, HttpServer, max_retries. welder bridges the
two with a pluggable name style — a policy handed to welder::welder that every
generated name flows through — plus a per-entity weld_as override for the cases
a rule can't capture.
By default nothing is renamed: welder::welder<Rod> binds each C++ identifier
verbatim (the style is welder::naming::none).
In the cookbook
Recipe 07 — One library, two languages binds one
camelCase C++ header with PEP 8 names for Python and snake_case for Lua, and
uses per-language weld_as to give one entity a different name in each.
A style is the second welder::welder argument¶
#include <welder/rods/python/pybind11/rod.hpp>
#include <welder/rods/python/naming.hpp> // welder::rods::python::pep8
using weld = welder::welder<welder::rods::pybind11::rod<>,
welder::rods::python::pep8>;
PYBIND11_MODULE(mymod, m) {
weld::weld_module<^^mymod>(m);
}
pep8 is the ready-made Python style: CapWords (PascalCase) for classes and
enum types, snake_case for methods, functions, fields and module variables, and
enum members left as authored. So:
>>> mymod.GeometryHelper # class: CapWords (unchanged here)
>>> h.process_file(...) # processFile -> process_file
>>> mymod.max_retries # maxRetries -> max_retries
The same style plugs into the nanobind rod, the sol2 rod, and the LuaCATS stub
generator — it is rod-agnostic. (For Lua you would typically pick a Lua-flavoured
style, or none; pep8 lives with the Python rods because that is its home.)
The style knows what it is naming¶
A style is asked to name each entity through a per-kind hook, because the same identifier is styled differently by kind — PEP 8 PascalCases a class but snake_cases a method:
namespace welder::naming {
struct my_style : /* a base */ {
static consteval std::string transform_class(std::meta::info);
static consteval std::string transform_enum(std::meta::info);
static consteval std::string transform_enumerator(std::meta::info);
static consteval std::string transform_method(std::meta::info);
static consteval std::string transform_static_method(std::meta::info);
static consteval std::string transform_function(std::meta::info);
static consteval std::string transform_field(std::meta::info);
static consteval std::string transform_variable(std::meta::info);
static consteval std::string transform_submodule(std::meta::info);
};
}
You never inspect the reflection to discover the kind; the driver already knows what
it is binding and calls the matching hook. In practice you inherit a base and
override only the hooks that differ. That is exactly how pep8 is built — inherit
the all-snake_case style, override classes/enums to CapWords, keep enum members
verbatim:
struct pep8 : welder::naming::snake_case {
static consteval std::string transform_class(std::meta::info e) {
return welder::naming::restyle(std::meta::identifier_of(e),
welder::naming::case_kind::pascal);
}
static consteval std::string transform_enum(std::meta::info e) {
return welder::naming::restyle(std::meta::identifier_of(e),
welder::naming::case_kind::pascal);
}
static consteval std::string transform_enumerator(std::meta::info e) {
return std::string{std::meta::identifier_of(e)}; // as authored
}
};
The word round-trip (source spelling doesn't matter)¶
The hard part is that the input format is unknown — an identifier might arrive as
snake_case, camelCase, PascalCase or SCREAMING_CASE. The core
(<welder/naming.hpp>) solves it by first splitting an identifier into its words
and then re-joining them in the target convention, so the result is stable
whatever the source:
welder::naming::split_words("HTTPServer") // -> {"http", "server"} (acronym run)
welder::naming::split_words("processFile") // -> {"process", "file"} (camel hump)
welder::naming::restyle("process_file", welder::naming::case_kind::pascal) // "ProcessFile"
welder::naming::restyle("maxRetries", welder::naming::case_kind::snake) // "max_retries"
Splitting breaks on underscores/hyphens, camel-case humps and acronym boundaries;
leading/trailing fixup underscores (_private, type_) survive. Every hook that
just applies one convention can be written with restyle.
Name styles¶
| Style | Effect |
|---|---|
welder::naming::none |
Identity — bind the C++ identifier unchanged (the default). |
welder::naming::snake_case |
foo_bar everywhere. |
welder::naming::pascal_case |
FooBar everywhere. |
welder::naming::camel_case |
fooBar everywhere. |
welder::naming::screaming_snake_case |
FOO_BAR everywhere. |
welder::naming::kebab_case |
foo-bar everywhere. |
welder::rods::python::pep8 |
PEP 8 mix (CapWords types, snake_case rest). |
The single-convention styles apply the same convention to every kind; use one
directly, or inherit it and override the exceptions (as pep8 does).
weld_as: force a name verbatim¶
Some names a rule can't derive — a C++ method whose Python name should be something
unrelated, or a per-language spelling. weld_as is the ultimate override: the string
is used verbatim and never flows through the style.
struct [[=welder::weld(welder::lang::py, welder::lang::lua)]]
[[=welder::policy::opt_in]]
FileProcessor {
[[=welder::mark::include]]
[[=welder::weld_as(welder::lang::py, "process")]] // -> process() in Python
[[=welder::weld_as(welder::lang::lua, "Process")]] // -> Process() in Lua
void processImpl();
[[=welder::mark::include, =welder::weld_as("VERSION")]] // every language, verbatim
static const char* version_string();
[[=welder::mark::include]]
[[=welder::weld_as(welder::lang::py, welder::lang::lua, "flush")]] // both, one name
void flushEverything();
};
The name is always the last argument; any languages it applies to come first.
weld_as("name") (no marker) covers every language; weld_as(lang, "name") scopes it
to one; weld_as(lang, lang, …, "name") names several at once. Repeat the annotation
to give a different name per language (as processImpl does above). Being verbatim,
it bypasses the name style entirely — welder does not touch the string.
A style or weld_as that renames a type is honoured everywhere it appears —
its declaration, and every reference to it: field/parameter/return types, base-class
lists, and inside container types (Rect[], table<string, Rect>). This holds for
the runtime bindings and for the generated LuaCATS (---@meta) stub
alike; in the stub, references are emitted with the raw C++ name and reconciled with
their declarations in a final pass, so declaration order never matters.
Next: The bindability gate.