Skip to content

Registration macros fail to compile for namespace-qualified model/action types #21

Description

@Yaraslaut

Summary

BRIDGE_REGISTER_MODEL / BRIDGE_REGISTER_ACTION build the name of their generated
static registrar by token-pasting the model and action types:

[[maybe_unused]] const bool bridge_action_reg_##M##_##A = ...

That works only when both arguments are bare identifiers. Passing a qualified type
pastes into an invalid preprocessor token and fails to compile.

Reproduction

namespace app::models {

struct Report {
    struct Create {
        static constexpr std::string_view Name = "Create";
        struct Payload { int id = 0; };
        struct Result  { bool ok = false; };
        using ReturnType = Result;
        Payload payload;
    };

    Create::Result execute(Create const&);
};

} // namespace app::models

// Unqualified — fine:
//   BRIDGE_REGISTER_MODEL(Report, "Report")

// Qualified — fails: the generated identifier contains ':' characters.
BRIDGE_REGISTER_MODEL(app::models::Report, "app.models.Report")
BRIDGE_REGISTER_ACTION(app::models::Report, app::models::Report::Create, "Create")

MSVC / clang-cl reject the pasted token; the error points at the macro expansion rather
than at the call site, which makes it slow to diagnose.

Why it matters

Any codebase that namespaces its models — or nests action types inside the model struct,
which the documented action shape encourages — cannot use the registration macros without
introducing a local alias purely to dodge the paste:

using ReportModel = app::models::Report;   // only exists to satisfy the macro
BRIDGE_REGISTER_MODEL(ReportModel, "app.models.Report")

The existing tests and examples all register unqualified types, so this never shows up
in-tree.

Suggested fix

Key the generated names on __LINE__ instead of on the type spelling. There is exactly
one registration per line, so it is equally unique, and it is independent of how the type
is written:

#define BRIDGE_DETAIL_CAT_(a, b) a##b
#define BRIDGE_DETAIL_CAT(a, b)  BRIDGE_DETAIL_CAT_(a, b)

[[maybe_unused]] const bool BRIDGE_DETAIL_CAT(bridge_action_reg_, __LINE__) = ...

Strictly more permissive: no behaviour change for existing callers, and unqualified
registrations keep compiling unchanged.

I'm happy to open a PR if the approach looks right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions