lumiera_documentation/src/lib/meta/fun-inline.hpp
Ichthyostega a0d83b4fe8 Library: investigate design for an opaque inline functor
This is a design study to find out what would be required
to build an opaque function wrapper, similar to `std::function`,
yet without using any heap memory ever.

Such a building block would allow for some interesting options
for a well structured design of elaborate data structures.

This changeset provides an implementation draft.

__Two important findings to note__:
 * it is impossible to get away with just a single function pointer
 * because at the very least we need to invoke the destructor of the
   embedded delegate. Thus something akin to a "manager" is required.
 * this implies that the component would not be so efficient as I hoped,
   since it requires two pointers and requires to emit code for each
   type of delegate.
 * by making the component **NonCopyable**, I avoided the complexities
   of virtual copy support. In case the latter is desired, it would be
   much simpler to switch to a classical OO approach with a VTable
   and use our `PolymorphicValue` wrapper to handle copy and cloning.
2026-04-08 01:53:53 +02:00

237 lines
7 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
FUN-INLINE.hpp - abstract functor with local delegate
Copyright (C)
2026 Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
*/
/** @file fun-inline.hpp
** A generic abstracted functor with the implementation stored locally.
** This template is an implementation building block and allows to create
** an abstraction barrier, similar to what std::function does. In contrast
** to the latter however, FunInline places the actual implementation delegate
** into an embedded opaque buffer, never using any heap memory.
**
** @todo WIP-WIP 4/2026 this is more like a conceptual draft,
** not sure where this design exercise will lead to.
** @warning after investigating the implementation technique to some degree,
** it became clear that **copy support** is the greatest challenge.
** All »the rest« might be surprisingly simple to implement, yet
** the demand for a movable / copyable value-object effectively
** translates into something like a VTable — and the result
** would be similar to lib::PolymorphicValue — just with
** some special binding to the function operator.
** For this reason, FunInline is now declared to be util::NonCopyable
** @todo 4/2026 I've finally got this draft hammered out.
** It is far from what I wanted to achieve...
** Notably a second function pointer is used
** That is ugly, but it does the job.
**
** @see FunInline_test
**
*/
#ifndef LIB_META_FUN_INLINE_H_
#define LIB_META_FUN_INLINE_H_
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/typeseq-util.hpp"
#include "lib/meta/variadic-helper.hpp"
#include <utility>
namespace lib {
namespace meta{
namespace err = lumiera::error;
using std::forward;
namespace test { class FunInline_test; }
/**
* An opaque front-end for a functor stored inline.
* The type of the implementation delegate is erased
* from the type, retaining only the generic signature.
* @note this wrapper object is defined as NonCopyable,
* because anything else would require a VTable,
* and raise serious concerns regarding identity.
* @warning deliberately **not threadsafe**
* @see FunInline_test
*/
template<class SIG
,size_t siz
,typename ALI = int64_t
>
class FunInline
{
static_assert (!sizeof(SIG), "function signature required");
};
template<typename RET, typename...ARGS
,size_t siz
,typename ALI
>
class FunInline<RET(ARGS...), siz, ALI>
: util::NonCopyable
{
using _F = _Fun<RET(ARGS...)>;
static_assert (_F{}, "function signature required");
using Sig = _F::Sig;
using Ret = _F::Ret;
using Args = _F::Args;
using RArgs = ElmTypes<Args>::template Apply<std::add_rvalue_reference_t>;
using IArgs = Prepend<void*, RArgs>::Seq;
using ISig = BuildFunType<Ret, IArgs>::Sig;
using Dtor = void(void*);
/** virtualised delegation through function pointer(s) */
ISig* invoker_{nullptr};
Dtor* destroy_{nullptr};
/** opaque storage buffer to emplace the functor delegate */
alignas(ALI) mutable
std::byte buf_[siz];
/**
* The Invoker template is instantiated with the type of the concrete
* delegate functor (which resides in the opaque storage buffer).
* The purpose of this template is to cause generation of a static
* delegation function that can be invoked through the #invoker_ pointer.
* In the typical usage situation, the delegate type \a FUNC is implemented
* mostly with inline functions, and thus the bulk of the actual implementation
* code will be emitted into this static delegation function. The template
* \ref InvokerBuilder provides the blueprint for this code generation.
*/
template<class FUN>
struct Invoker
{
static Ret
dispatch (void* instance, ARGS&&...args)
{
return functor(instance) (forward<ARGS> (args)...);
}
static void
destroy (void* instance)
{
if (instance)
functor(instance).~FUN();
}
static FUN&
functor (void* instance)
{
REQUIRE (instance);
return * static_cast<FUN*> (instance);
}
using Sig = _F::Sig;
using ISig = decltype(dispatch);
};
/** allow the Unit test to poke into internals */
friend class FunInline_test;
public:
~FunInline(){ destroy(); }
FunInline() = default;
template<class FUNC>
FunInline (FUNC&& functor)
{
using Functor = std::decay_t<FUNC>;
emplace<Functor> (forward<FUNC> (functor));
activate<Functor>();
}
// NonCopyable
operator bool() const
{
return bool(destroy_) and bool(invoker_);
}
/* == Functor interface == */
Ret
operator() (ARGS&&... args)
{
if (not invoker_)
throw err::State{"Attempt to invoke inactive function."};
return (*invoker_) (&buf_, forward<ARGS> (args)...);
}
private: /* == Instance management == */
void
destroy()
{
if (destroy_)
(*destroy_) (&buf_);
destroy_ = nullptr;
invoker_ = nullptr;
}
template<class FUNC>
void
activate()
{
ASSERT_VALID_SIGNATURE (FUNC, Sig);
REQUIRE (!destroy_ and !invoker_, "Lifecycle logic broken");
destroy_ = Invoker<FUNC>::destroy;
invoker_ = Invoker<FUNC>::dispatch;
}
/** Abbreviation for placement new */
template<class TY, typename...INIT>
TY&
emplace (INIT&& ...args)
{
static_assert (siz >= sizeof(TY), "Embedded storage buffer too small");
static_assert (alignof(ALI) >= alignof(TY), "Alignment requirements not fulfilled");
destroy();
try {
return *new(&buf_) TY {std::forward<INIT> (args)...};
}
catch (...)
{
ENSURE (not destroy_);
throw;
}
}
/// @todo unused ... not required?
template<typename XXX>
XXX&
accessAs() const
{
return * std::launder (reinterpret_cast<XXX*> (&buf_));
}
};
}}// namespace lib::meta
#endif /*LIB_META_FUN_INLINE_H_*/