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 <Roland.Reichwein@bmw.de>
This commit is contained in:
Drew Rife 2026-06-24 11:51:02 -04:00 committed by GitHub
parent 05010aebf8
commit e6a7642121
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 104 additions and 19 deletions

View File

@ -161,6 +161,19 @@ constexpr auto d = etl::delegate<int(int)>::create<Test, test, &Test::member_fun
All create functions are `constexpr` under C++14.
All create functions are `[[nodiscard]]` under C++17.
`>=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<int(int)>(&global);
constexpr auto d2 = etl::delegate<int(int)>::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.

View File

@ -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<TArgs...>;
//*************************************************************************
@ -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<TLambda>::value && !is_delegate<TLambda>::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<TLambda>::value && !is_delegate<TLambda>::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 <typename TLambda,
typename = etl::enable_if_t<etl::is_class<TLambda>::value && !etl::is_same<etl::delegate<TReturn(TArgs...)>, 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;
//*************************************************************************

View File

@ -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<void(void)>;
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