From 2ee6d8eb3a9ec9f4e45810f8e9eba9d8ae83885b Mon Sep 17 00:00:00 2001 From: Adam Mitchell Date: Fri, 31 Jul 2026 12:43:12 +0000 Subject: [PATCH] Add support for named function parameters --- include/thingset++/ThingSetFunction.hpp | 62 +++++++++++++++++++++++-- tests/TestFunctions.cpp | 21 ++++++++- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/include/thingset++/ThingSetFunction.hpp b/include/thingset++/ThingSetFunction.hpp index f1680e0..b635b71 100644 --- a/include/thingset++/ThingSetFunction.hpp +++ b/include/thingset++/ThingSetFunction.hpp @@ -38,16 +38,39 @@ static bool invoke(std::function &function, std::tuple return encoder.encodeNull(); } +/// @brief Carries optional human-friendly names for a function's parameters. +/// Pass as the ParamNames argument of ThingSetFunction (or use one of the +/// ThingSetNamed*Function aliases). An empty list means every parameter +/// name is auto-generated from the function name, argument type and +/// position (e.g. "xOnu16_1"); a non-empty list must name every argument. +/// Names travel in the node metadata +template +struct ThingSetParameterNames +{ + static constexpr size_t count = sizeof...(Names); + + template + static constexpr auto get() + { + return std::get(std::tuple{ Names... }); + } +}; + /// @brief Represents an executable function. /// @tparam Id The unique integer ID of the ThingSet node. /// @tparam ParentId The integer ID of the parent node. /// @tparam Name The name of the node. /// @tparam Access Access control flags. +/// @tparam ParamNames A ThingSetParameterNames<...> naming each argument, +/// or ThingSetParameterNames<> to auto-generate names. /// @tparam Result The return type of the function. /// @tparam ...Args The argument types of the function, if any. -template +template class ThingSetFunction : public IdentifiableThingSetParentNode, public ThingSetInvocable { + static_assert(ParamNames::count == 0 || ParamNames::count == sizeof...(Args), + "ThingSetParameterNames must either be empty or name every argument"); + private: template class ThingSetFunctionParameter : public IdentifiableThingSetNode @@ -82,7 +105,18 @@ class ThingSetFunction : public IdentifiableThingSetParentNode; using ParameterType = std::tuple_element_t; - typedef ThingSetFunctionParameter>::name + "_" + to_string_t<1 + Index>(), ParameterType> type; + + static constexpr auto argName() + { + if constexpr (ParamNames::count != 0) { + return ParamNames::template get(); + } + else { + return Name + ThingSetType>::name + "_" + to_string_t<1 + Index>(); + } + } + + typedef ThingSetFunctionParameter type; }; /// @brief The exposed function. @@ -133,13 +167,31 @@ class ThingSetFunction : public IdentifiableThingSetParentNode -using ThingSetUserFunction = ThingSetFunction; +using ThingSetUserFunction = + ThingSetFunction, Result, Args...>; template -using ThingSetAdvancedFunction = ThingSetFunction; +using ThingSetAdvancedFunction = + ThingSetFunction, Result, Args...>; template using ThingSetManufacturerFunction = - ThingSetFunction; + ThingSetFunction, Result, + Args...>; + +// Named variants: identical to the above but with human-friendly parameter +// names, e.g. +// ThingSetNamedUserFunction<0x710, 0x07, "xOn", ThingSetParameterNames<"uSwitchMode">, int, uint16_t> +template +using ThingSetNamedUserFunction = + ThingSetFunction; + +template +using ThingSetNamedAdvancedFunction = + ThingSetFunction; + +template +using ThingSetNamedManufacturerFunction = + ThingSetFunction; } // namespace ThingSet \ No newline at end of file diff --git a/tests/TestFunctions.cpp b/tests/TestFunctions.cpp index 7adbaf2..2bbcb21 100644 --- a/tests/TestFunctions.cpp +++ b/tests/TestFunctions.cpp @@ -31,8 +31,12 @@ ThingSetUserFunction<0x430, 0x0, "xMap", std::map> xGetMap([ map.insert_or_assign("world", 2.0f); return map; }); -ThingSetFunction<0x440, 0x0, "xTestCustom", ThingSetAccess::anyWrite, 0x4401, void, int, float> xTestCustomFunc(test); +ThingSetFunction<0x440, 0x0, "xTestCustom", ThingSetAccess::anyWrite, 0x4401, ThingSetParameterNames<>, void, int, + float> + xTestCustomFunc(test); ThingSetUserFunction<0x450, 0x0, "xString", int, std::string &> xString([](std::string &) { return 0; }); +ThingSetNamedUserFunction<0x470, 0x0, "xNamed", ThingSetParameterNames<"uAlpha", "uBeta">, int, uint16_t, float> + xNamed([](uint16_t, float) { return 0; }); TEST(Functions, FunctionTypes) { @@ -62,6 +66,21 @@ TEST(Functions, FunctionParameters) ASSERT_EQ("string", node->getType()); } +TEST(Functions, NamedFunctionParameters) +{ + ThingSetNode *node; + ASSERT_TRUE(ThingSetRegistry::findById(0x471, &node)); + ASSERT_EQ(0x470, node->getParentId()); + ASSERT_EQ("uAlpha", node->getName()); + ASSERT_EQ("u16", node->getType()); + ASSERT_TRUE(ThingSetRegistry::findById(0x472, &node)); + ASSERT_EQ(0x470, node->getParentId()); + ASSERT_EQ("uBeta", node->getName()); + ASSERT_EQ("f32", node->getType()); + // Names are metadata only: the function's type signature is unchanged. + ASSERT_EQ("(u16,f32)->(i32)", xNamed.getType()); +} + TEST(Functions, FunctionParametersWithCustomId) { ThingSetNode *node;