From a06c41f2c7564bbe1145b91f44efecc77b476d4b Mon Sep 17 00:00:00 2001 From: Romain BOULLARD Date: Wed, 29 Jul 2026 23:11:48 +0200 Subject: [PATCH] Simplify templates --- docs/source/languages/cpp.md | 16 +++++++++++----- src/idl_parser.cpp | 35 ++++++++--------------------------- tests/native_type_test.fbs | 4 ++-- tests/parser_test.cpp | 8 -------- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/docs/source/languages/cpp.md b/docs/source/languages/cpp.md index 3bd59e5bd..1a3d9071b 100644 --- a/docs/source/languages/cpp.md +++ b/docs/source/languages/cpp.md @@ -242,14 +242,19 @@ provide the following functions to aide in the serialization process: } ``` -- `native_type_template("type<{{T}}>")` (on a struct) together with +- `native_type_template("type")` (on a struct) together with `native_type_template_arg("type")` (on a field of that struct type): lets a single struct declaration back a native type that is a C++ template, instantiated differently per field, instead of requiring one - struct declaration (and `native_type`) per instantiation. For example: + struct declaration (and `native_type`) per instantiation. + `native_type_template_arg` is angle-bracket-appended to + `native_type_template` to form the field's native type, so + `native_type_template: "Native::Box"` with + `native_type_template_arg: "Native::TypeA"` produces + `Native::Box`. For example: ```cpp - struct Box (native_type_template: "Native::Box<{{T}}>") { + struct Box (native_type_template: "Native::Box") { value: int32; } @@ -271,10 +276,10 @@ is equivalent to writing, on each field itself: functions stay unique across instantiations without spelling it out yourself; an explicit `native_type_pack_name` on the field still overrides this. More than one template parameter is supported by separating them with -commas and using indexed placeholders: +commas: ```cpp - struct Pair (native_type_template: "Native::Pair<{{T0}}, {{T1}}>") { + struct Pair (native_type_template: "Native::Pair") { value: int32; } @@ -283,6 +288,7 @@ commas and using indexed placeholders: } ``` +which produces `Native::Pair`. `native_type_template_arg` is only valid on fields whose type is a struct (or vector of structs) that declares `native_type_template`. diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 0a82156db..e5014773e 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -1337,35 +1337,16 @@ CheckedError Parser::ParseField(StructDef& struct_def) { } } - // Substitute each argument for its placeholder: `{{T0}}`, `{{T1}}`, etc. - // For a single argument, the unindexed `{{T}}` spelling is also - // accepted (equivalent to `{{T0}}`). - std::string native_type_str = native_type_template->constant; + // The native type is `native_type_template`, the bare template name, + // with the arguments appended as a trailing, angle-bracketed template + // argument list, e.g. "Native::Box" + ["Native::TypeA"] -> + // "Native::Box". + std::string native_type_str = native_type_template->constant + "<"; for (size_t i = 0; i < args.size(); ++i) { - std::vector placeholders = { "{{T" + NumToString(i) + - "}}" }; - if (i == 0) placeholders.push_back("{{T}}"); - size_t replaced = 0; - for (const auto& placeholder : placeholders) { - for (;;) { - const auto pos = native_type_str.find(placeholder); - if (pos == std::string::npos) break; - native_type_str.replace(pos, placeholder.length(), args[i]); - ++replaced; - } - } - if (!replaced) - return Error("'native_type_template' on '" + target_struct->name + - "' does not reference the '{{T" + NumToString(i) + - "}}' placeholder required for template argument " + - NumToString(i + 1) + " ('" + args[i] + "')"); + if (i) native_type_str += ", "; + native_type_str += args[i]; } - if (native_type_str.find("{{T") != std::string::npos) - return Error( - "'native_type_template' on '" + target_struct->name + - "' references a '{{T}}' placeholder beyond the " + - NumToString(args.size()) + - " template argument(s) given in 'native_type_template_arg'"); + native_type_str += ">"; auto native_type_val = new Value(); native_type_val->type = native_type_template_arg->type; diff --git a/tests/native_type_test.fbs b/tests/native_type_test.fbs index b8d84b876..5699240c1 100644 --- a/tests/native_type_test.fbs +++ b/tests/native_type_test.fbs @@ -24,12 +24,12 @@ table Matrix (native_type:"Native::Matrix") { // field via `native_type_template_arg` instead of needing one flatbuffers // struct declaration per specialization (compare to Vector3D/Vector3DAlt // above). -struct Tagged (native_type_template: "Native::Tagged<{{T}}>") { +struct Tagged (native_type_template: "Native::Tagged") { value:int32; } // A native type template that takes more than one argument. -struct Pair (native_type_template: "Native::Pair<{{T0}}, {{T1}}>") { +struct Pair (native_type_template: "Native::Pair") { value:int32; } diff --git a/tests/parser_test.cpp b/tests/parser_test.cpp index 0c8260e93..905996b41 100644 --- a/tests/parser_test.cpp +++ b/tests/parser_test.cpp @@ -106,14 +106,6 @@ void ErrorTest() { "struct-typed fields"); TestError( "struct X (native_type_template: \"Foo\") { Y:int; } " - "table T { y:X (native_type_template_arg:\"int\"); }", - "does not reference"); - TestError( - "struct X (native_type_template: \"Foo<{{T0}}, {{T1}}>\") { Y:int; } " - "table T { y:X (native_type_template_arg:\"int\"); }", - "beyond the"); - TestError( - "struct X (native_type_template: \"Foo<{{T}}>\") { Y:int; } " "table T { y:X (native_type_template_arg:\"\"); }", "empty template argument"); TestError("{}", "no root");