From 69e8e9f3f1f4a00775af60c0ce077c6ec7f16897 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 14 Jan 2025 16:51:12 +0100 Subject: [PATCH] base: add util/callable.h Fixes #5420 --- repos/base/include/util/callable.h | 95 +++++++++++++++++++ repos/base/recipes/pkg/test-callable/README | 1 + repos/base/recipes/pkg/test-callable/archives | 1 + repos/base/recipes/pkg/test-callable/hash | 1 + repos/base/recipes/pkg/test-callable/runtime | 17 ++++ .../base/recipes/src/test-callable/content.mk | 2 + repos/base/recipes/src/test-callable/hash | 1 + .../base/recipes/src/test-callable/used_apis | 1 + repos/base/src/test/callable/main.cc | 78 +++++++++++++++ repos/base/src/test/callable/target.mk | 3 + repos/gems/run/depot_autopilot.run | 1 + 11 files changed, 201 insertions(+) create mode 100644 repos/base/include/util/callable.h create mode 100644 repos/base/recipes/pkg/test-callable/README create mode 100644 repos/base/recipes/pkg/test-callable/archives create mode 100644 repos/base/recipes/pkg/test-callable/hash create mode 100644 repos/base/recipes/pkg/test-callable/runtime create mode 100644 repos/base/recipes/src/test-callable/content.mk create mode 100644 repos/base/recipes/src/test-callable/hash create mode 100644 repos/base/recipes/src/test-callable/used_apis create mode 100644 repos/base/src/test/callable/main.cc create mode 100644 repos/base/src/test/callable/target.mk diff --git a/repos/base/include/util/callable.h b/repos/base/include/util/callable.h new file mode 100644 index 00000000000..f28ab40e626 --- /dev/null +++ b/repos/base/include/util/callable.h @@ -0,0 +1,95 @@ +/* + * \brief Utility for passing lambda arguments to non-template functions + * \author Norman Feske + * \date 2025-01-14 + */ + +/* + * Copyright (C) 2025 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INCLUDE__UTIL__CALLABLE_H_ +#define _INCLUDE__UTIL__CALLABLE_H_ + +#include + +namespace Genode { template struct Callable; }; + + +/** + * Utility for passing lambda arguments to non-template functions + * + * A function or method taking a lambda as argument must always be a template + * because the captured state is known only at the caller site. For example, + * within a display driver, the following function is meant to call 'fn' for + * each 'Connector const &' that is currently known. + * + * ! void for_each_connector(auto const &fn); + * + * The type of 'fn' as template parameter stands in the way in two situations. + * First, 'for_each_connector' must be implemented in a header because it is a + * template. But its inner working might be complex and better be hidden inside + * a compilation unit. Second, 'for_each_connector' cannot be part of an + * abstract interface because template methods cannot be virtual functions. + * + * The 'Callable' utility addresses both situations by introducing an abstract + * function type 'Ft' for the plain signature (arguments, return type) of a + * lambda, and an 'Fn' type implementing the abstract function for a concrete + * lambda argument. E.g., the following 'With_connector' type defines a + * signature for a lambda that takes a 'Connector const &' argument. + * + * ! With_connector = Callable; + * + * The 'With_connector' definition now allows for defining a pure virtual + * function by referring to the signature's function type 'Ft'. It is a good + * practice to use a '_' prefix because this method is not meant to be the API. + * + * ! virtual void _for_each_connector(With_connector::Ft const &) = 0; + * + * The user-facing API should best accept a regular 'auto const &fn' argument + * and call the virtual function with a concrete implementation of the function + * type, which is accomplished via the 'Fn' type. + * + * ! void for_each_connector(auto const &fn) + * ! { + * ! _for_each_connector( With_connector::Fn { fn } ); + * ! } + * + * At the implementation site of '_for_each_connector', the 'Ft' argument + * can be called like a regular lambda argument. At the caller site, + * 'for_each_connector' accepts a regular lambda argument naturally expecting + * a 'Connector const &'. + */ +template +struct Genode::Callable +{ + struct Ft : Interface { virtual RET operator () (ARGS &&...) const = 0; }; + + template + struct Fn : Ft + { + FN const &_fn; + Fn(FN const &fn) : _fn(fn) { }; + RET operator () (ARGS &&... args) const override { return _fn(args...); } + }; +}; + + +template +struct Genode::Callable +{ + struct Ft : Interface { virtual void operator () (ARGS &&...) const = 0; }; + + template + struct Fn : Ft + { + FN const &_fn; + Fn(FN const &fn) : _fn(fn) { }; + void operator () (ARGS &&... args) const override { _fn(args...); } + }; +}; + +#endif /* _INCLUDE__UTIL__CALLABLE_H_ */ diff --git a/repos/base/recipes/pkg/test-callable/README b/repos/base/recipes/pkg/test-callable/README new file mode 100644 index 00000000000..a11c7ef1ece --- /dev/null +++ b/repos/base/recipes/pkg/test-callable/README @@ -0,0 +1 @@ +Scenario that tests the util/callable.h utility diff --git a/repos/base/recipes/pkg/test-callable/archives b/repos/base/recipes/pkg/test-callable/archives new file mode 100644 index 00000000000..d09094d232b --- /dev/null +++ b/repos/base/recipes/pkg/test-callable/archives @@ -0,0 +1 @@ +_/src/test-callable diff --git a/repos/base/recipes/pkg/test-callable/hash b/repos/base/recipes/pkg/test-callable/hash new file mode 100644 index 00000000000..ad76672ca46 --- /dev/null +++ b/repos/base/recipes/pkg/test-callable/hash @@ -0,0 +1 @@ +2025-01-14 74d8426380b0552c3c26487469c0aeeec32cbde0 diff --git a/repos/base/recipes/pkg/test-callable/runtime b/repos/base/recipes/pkg/test-callable/runtime new file mode 100644 index 00000000000..b3be06210f3 --- /dev/null +++ b/repos/base/recipes/pkg/test-callable/runtime @@ -0,0 +1,17 @@ + + + + + [init] result of action.compute: 34 + [init] accessing XML node, state=reset + [init] --- finished callable test --- + + + + + + + + + + diff --git a/repos/base/recipes/src/test-callable/content.mk b/repos/base/recipes/src/test-callable/content.mk new file mode 100644 index 00000000000..9510e07c8f4 --- /dev/null +++ b/repos/base/recipes/src/test-callable/content.mk @@ -0,0 +1,2 @@ +SRC_DIR = src/test/callable +include $(GENODE_DIR)/repos/base/recipes/src/content.inc diff --git a/repos/base/recipes/src/test-callable/hash b/repos/base/recipes/src/test-callable/hash new file mode 100644 index 00000000000..4ebcd981568 --- /dev/null +++ b/repos/base/recipes/src/test-callable/hash @@ -0,0 +1 @@ +2025-01-14 86e453af4b2c1696028b76af2d459b3891550f11 diff --git a/repos/base/recipes/src/test-callable/used_apis b/repos/base/recipes/src/test-callable/used_apis new file mode 100644 index 00000000000..df967b96a57 --- /dev/null +++ b/repos/base/recipes/src/test-callable/used_apis @@ -0,0 +1 @@ +base diff --git a/repos/base/src/test/callable/main.cc b/repos/base/src/test/callable/main.cc new file mode 100644 index 00000000000..9fb0af46b8e --- /dev/null +++ b/repos/base/src/test/callable/main.cc @@ -0,0 +1,78 @@ +/* + * \brief Test for the Callable utility + * \author Norman Feske + * \date 2025-01-14 + */ + +/* + * Copyright (C) 2025 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#include +#include +#include + +using namespace Genode; + + +struct Action : Interface +{ + /* + * A functor argument taking 3 ints and returning one int. + */ + using With_3_numbers = Callable; + + virtual int _compute(With_3_numbers::Ft const &) const = 0; + + int compute(auto const &fn) const { return _compute( With_3_numbers::Fn { fn } ); } + + + /* + * A functor argument taking an Xml_node const &, without return value + */ + using With_xml_node = Callable; + + virtual void _with_xml(With_xml_node::Ft const &) = 0; + + void with_xml(auto const &fn) { _with_xml( With_xml_node::Fn { fn } ); } +}; + + +static void test(Action &action) +{ + int const result = action.compute([&] (int a, int b, int c) { + return a + b + c; }); + + log("result of action.compute: ", result); + + action.with_xml([&] (Xml_node const &node) { + log("accessing XML node, state=", + node.attribute_value("state", String<16>())); }); +} + + +void Component::construct(Env &) +{ + log("--- callable test ---"); + + struct Test_action : Action + { + int _compute(With_3_numbers::Ft const &fn) const override + { + return fn(10, 11, 13); + } + + void _with_xml(With_xml_node::Ft const &fn) override + { + Xml_node const node { "" }; + fn(node); + } + } action { }; + + test(action); + + log("--- finished callable test ---"); +} diff --git a/repos/base/src/test/callable/target.mk b/repos/base/src/test/callable/target.mk new file mode 100644 index 00000000000..77fffc14ea3 --- /dev/null +++ b/repos/base/src/test/callable/target.mk @@ -0,0 +1,3 @@ +TARGET = test-callable +SRC_CC = main.cc +LIBS = base diff --git a/repos/gems/run/depot_autopilot.run b/repos/gems/run/depot_autopilot.run index c93006ac5fd..58e8ee3b5fa 100644 --- a/repos/gems/run/depot_autopilot.run +++ b/repos/gems/run/depot_autopilot.run @@ -653,6 +653,7 @@ set default_test_pkgs { test-spark_secondary_stack test-alarm test-black_hole + test-callable test-clipboard test-depot_query_index test-ds_ownership