From e6a7642121b4bd34ee96e0edce26f4c071bfe495 Mon Sep 17 00:00:00 2001 From: Drew Rife Date: Wed, 24 Jun 2026 11:51:02 -0400 Subject: [PATCH] Add constexpr support for delegate construction and assignment (#1476) * feat: Add constexpr support for delegate construction and assignment in C++14 and C++20 * docs: add more docstring coverage #1473 * docs: note constexpr availability for function-pointer overloads Update docs/callbacks/delegate.md to reflect the new ETL_CONSTEXPR14 annotations on delegate(function_ptr) and create(function_ptr), and add a paragraph to the Constexpr section explaining which function-pointer APIs are usable in constant expressions at C++14 vs C++20. #1473 --------- Co-authored-by: Roland Reichwein --- docs/callbacks/delegate.md | 21 ++++++++-- include/etl/private/delegate_cpp11.h | 60 ++++++++++++++++++++-------- test/test_delegate.cpp | 42 +++++++++++++++++++ 3 files changed, 104 insertions(+), 19 deletions(-) diff --git a/docs/callbacks/delegate.md b/docs/callbacks/delegate.md index f3dd7bc6..a07a1153 100644 --- a/docs/callbacks/delegate.md +++ b/docs/callbacks/delegate.md @@ -161,6 +161,19 @@ constexpr auto d = etl::delegate::create=TBC` +The runtime function-pointer overloads `delegate(function_ptr)` and +`create(function_ptr)` are usable in constant expressions from C++14. +The mutating overloads `set(function_ptr)` and `operator=(function_ptr)` are +usable in constant expressions from C++20 (they switch the active member of +an internal union, which became permitted in constant expressions in C++20 +per [P1330R0](https://wg21.link/P1330R0)). + +```cpp +constexpr auto d1 = etl::delegate(&global); +constexpr auto d2 = etl::delegate::create(&global); +``` + ### Calling the delegate The delegate may be called as a function with the defined parameter signature. @@ -195,11 +208,12 @@ Copy constructs a delegate. --- ```cpp -delegate(TReturn (*)(TArgs...) fp) ETL_NOEXCEPT +explicit ETL_CONSTEXPR14 delegate(TReturn (*)(TArgs...) fp) ETL_NOEXCEPT ``` **Description** Constructs from a run-time function pointer, or non-capturing lambda converted to a function pointer. -*From `20.47.0`* +*From `20.47.0`* +*Constexpr from `TBC`* ## Creation @@ -218,11 +232,12 @@ A constructed delegate. ```cpp ETL_NODISCARD -static delegate create(function_ptr fp) ETL_NOEXCEPT +static ETL_CONSTEXPR14 delegate create(function_ptr fp) ETL_NOEXCEPT ``` **Description** Create from a run-time function pointer. *From `20.47.0`* +*Constexpr from `TBC`* **Returns** A constructed delegate. diff --git a/include/etl/private/delegate_cpp11.h b/include/etl/private/delegate_cpp11.h index 27b443c0..0770e0f0 100644 --- a/include/etl/private/delegate_cpp11.h +++ b/include/etl/private/delegate_cpp11.h @@ -121,12 +121,18 @@ namespace etl { private: - using object_ptr = void*; + /// Type-erased pointer to the bound object instance. + using object_ptr = void*; + + /// Pointer type for a runtime-bound free function with the delegate's signature. using function_ptr = TReturn (*)(TArgs...); public: - using return_type = TReturn; + /// The delegate's return type. + using return_type = TReturn; + + /// Type list of the delegate's argument types. using argument_types = etl::type_list; //************************************************************************* @@ -137,12 +143,12 @@ namespace etl } //************************************************************************* - // Copy constructor. + /// Copy constructor. //************************************************************************* ETL_CONSTEXPR14 delegate(const delegate& other) = default; //************************************************************************* - // Construct from lambda or functor. + /// Construct from a lambda or functor. //************************************************************************* template < typename TLambda, typename = etl::enable_if_t< etl::is_class::value && !is_delegate::value, void>> ETL_CONSTEXPR14 delegate(TLambda& instance) ETL_NOEXCEPT @@ -151,7 +157,7 @@ namespace etl } //************************************************************************* - // Construct from const lambda or functor. + /// Construct from a const lambda or functor. //************************************************************************* template < typename TLambda, typename = etl::enable_if_t< etl::is_class::value && !is_delegate::value, void>> ETL_CONSTEXPR14 delegate(const TLambda& instance) ETL_NOEXCEPT @@ -160,8 +166,8 @@ namespace etl } //************************************************************************* - // Delete construction from rvalue reference lambda or functor. - // Excludes non-capturing lambdas convertible to a function pointer. + /// Delete construction from an rvalue reference lambda or functor. + /// Excludes non-capturing lambdas convertible to a function pointer. //************************************************************************* template ::value && !etl::is_same, TLambda>::value @@ -170,11 +176,11 @@ namespace etl ETL_CONSTEXPR14 delegate(TLambda&& instance) = delete; //************************************************************************* - // Construct from a function pointer. + /// Construct from a function pointer. //************************************************************************* - explicit delegate(function_ptr fp) ETL_NOEXCEPT + explicit ETL_CONSTEXPR14 delegate(function_ptr fp) ETL_NOEXCEPT + : invocation(fp, function_ptr_stub) { - assign(fp, function_ptr_stub); } //************************************************************************* @@ -208,10 +214,10 @@ namespace etl } //************************************************************************* - // Create from a function pointer. + /// Create from a function pointer. //************************************************************************* ETL_NODISCARD - static delegate create(function_ptr fp) ETL_NOEXCEPT + static ETL_CONSTEXPR14 delegate create(function_ptr fp) ETL_NOEXCEPT { return delegate(fp, function_ptr_stub); } @@ -333,9 +339,9 @@ namespace etl } //************************************************************************* - // Set from a function pointer. + /// Set from a function pointer. //************************************************************************* - void set(function_ptr fp) ETL_NOEXCEPT + ETL_CONSTEXPR14 void set(function_ptr fp) ETL_NOEXCEPT { assign(fp, function_ptr_stub); } @@ -530,9 +536,9 @@ namespace etl } //************************************************************************* - // Create from a function pointer. + /// Assign from a function pointer. //************************************************************************* - delegate& operator=(function_ptr fp) ETL_NOEXCEPT + ETL_CONSTEXPR14 delegate& operator=(function_ptr fp) ETL_NOEXCEPT { if (fp == ETL_NULLPTR) { @@ -623,8 +629,11 @@ namespace etl //************************************************************************* struct invocation_element { + /// Signature of the dispatch stub used to invoke the bound callable. using stub_type = TReturn (*)(const invocation_element&, TArgs...); + //*********************************************************************** + /// Constructs an empty invocation element (no bound callable). //*********************************************************************** ETL_CONSTEXPR14 invocation_element() ETL_NOEXCEPT : ptr(object_ptr(ETL_NULLPTR)) @@ -632,6 +641,9 @@ namespace etl { } + //*********************************************************************** + /// Constructs an invocation element with only a stub (used for + /// compile-time bindings that need no associated object pointer). //*********************************************************************** ETL_CONSTEXPR14 invocation_element(stub_type stub_) ETL_NOEXCEPT : ptr() @@ -639,6 +651,8 @@ namespace etl { } + //*********************************************************************** + /// Constructs an invocation element bound to an object pointer and stub. //*********************************************************************** ETL_CONSTEXPR14 invocation_element(object_ptr object_, stub_type stub_) ETL_NOEXCEPT : ptr(object_) @@ -646,6 +660,8 @@ namespace etl { } + //*********************************************************************** + /// Constructs an invocation element bound to a function pointer and stub. //*********************************************************************** ETL_CONSTEXPR14 invocation_element(function_ptr fp_, stub_type stub_) ETL_NOEXCEPT : ptr(fp_) @@ -653,37 +669,48 @@ namespace etl { } + //*********************************************************************** + /// Equality compares the stub and the active pointer member. //*********************************************************************** ETL_CONSTEXPR14 bool operator==(const invocation_element& rhs) const ETL_NOEXCEPT { return (rhs.stub == stub) && ((stub == function_ptr_stub) ? (rhs.ptr.fp == ptr.fp) : (rhs.ptr.object == ptr.object)); } + //*********************************************************************** + /// Inequality compares the stub and the active pointer member. //*********************************************************************** ETL_CONSTEXPR14 bool operator!=(const invocation_element& rhs) const ETL_NOEXCEPT { return !operator==(rhs); } + //*********************************************************************** + /// Resets the invocation element to the empty state. //*********************************************************************** ETL_CONSTEXPR14 void clear() ETL_NOEXCEPT { stub = ETL_NULLPTR; } + //*********************************************************************** + /// Storage holding either an object pointer or a function pointer. //*********************************************************************** union ptr_type { + /// Default-constructs with a null object pointer as the active member. ETL_CONSTEXPR14 ptr_type() ETL_NOEXCEPT : object(ETL_NULLPTR) { } + /// Constructs with the object pointer as the active member. ETL_CONSTEXPR14 ptr_type(object_ptr object_) ETL_NOEXCEPT : object(object_) { } + /// Constructs with the function pointer as the active member. ETL_CONSTEXPR14 ptr_type(function_ptr fp_) ETL_NOEXCEPT : fp(fp_) { @@ -697,6 +724,7 @@ namespace etl stub_type stub; }; + /// Function pointer type used to dispatch through the invocation stub. using stub_type = typename invocation_element::stub_type; //************************************************************************* diff --git a/test/test_delegate.cpp b/test/test_delegate.cpp index d57ad1f8..f990e115 100644 --- a/test/test_delegate.cpp +++ b/test/test_delegate.cpp @@ -2125,6 +2125,48 @@ namespace (void)d; } #endif + + #if ETL_USING_CPP14 + //************************************************************************* + // Verify that constructing / creating / setting / assigning a delegate + // from a free function pointer is usable in a constant expression. + //************************************************************************* + TEST(test_constexpr_function_ptr_construction) + { + using delegate_type = etl::delegate; + + constexpr delegate_type d1(&free_void); + constexpr delegate_type d2 = delegate_type::create(&free_void); + + static_assert(d1 == d2, "constexpr-constructed delegates should compare equal"); + + #if ETL_USING_CPP20 + // Exercise constexpr set() and operator= in a constant-evaluated + // mutating context. Requires C++20 because the underlying union must + // switch its active member, which is only permitted in constant + // expressions from C++20 onwards (P1330R0). + constexpr auto make_via_set = []() constexpr + { + delegate_type d; + d.set(&free_void); + return d; + }; + + constexpr auto make_via_assign = []() constexpr + { + delegate_type d; + d = &free_void; + return d; + }; + + constexpr delegate_type d3 = make_via_set(); + constexpr delegate_type d4 = make_via_assign(); + + static_assert(d3 == d1, ""); + static_assert(d4 == d1, ""); + #endif + } + #endif } } // namespace