Compare commits
11 Commits
c9c2bc11ae
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1199c8ac07 | |||
| 5b877350c9 | |||
| 0285bc402e | |||
| f8681441e2 | |||
| 3f4d5b30a5 | |||
| 68a316354d | |||
| aaa77f7ea5 | |||
| aa8edf76e5 | |||
| f266f83729 | |||
| cc0688f048 | |||
| 6d7cfc486e |
74
templatefsm/.gitignore
vendored
Normal file
74
templatefsm/.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# This file is used to ignore files which are generated
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.autosave
|
||||||
|
*.a
|
||||||
|
*.core
|
||||||
|
*.moc
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*_pch.h.cpp
|
||||||
|
*_resource.rc
|
||||||
|
*.qm
|
||||||
|
.#*
|
||||||
|
*.*#
|
||||||
|
core
|
||||||
|
!core/
|
||||||
|
tags
|
||||||
|
.DS_Store
|
||||||
|
.directory
|
||||||
|
*.debug
|
||||||
|
Makefile*
|
||||||
|
*.prl
|
||||||
|
*.app
|
||||||
|
moc_*.cpp
|
||||||
|
ui_*.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Thumbs.db
|
||||||
|
*.res
|
||||||
|
*.rc
|
||||||
|
/.qmake.cache
|
||||||
|
/.qmake.stash
|
||||||
|
|
||||||
|
# qtcreator generated files
|
||||||
|
*.pro.user*
|
||||||
|
CMakeLists.txt.user*
|
||||||
|
|
||||||
|
# xemacs temporary files
|
||||||
|
*.flc
|
||||||
|
|
||||||
|
# Vim temporary files
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
# Visual Studio generated files
|
||||||
|
*.ib_pdb_index
|
||||||
|
*.idb
|
||||||
|
*.ilk
|
||||||
|
*.pdb
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.vcproj
|
||||||
|
*vcproj.*.*.user
|
||||||
|
*.ncb
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.vcxproj
|
||||||
|
*vcxproj.*
|
||||||
|
|
||||||
|
# MinGW generated files
|
||||||
|
*.Debug
|
||||||
|
*.Release
|
||||||
|
|
||||||
|
# Python byte code
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
# --------
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
||||||
295
templatefsm/fsm.h
Normal file
295
templatefsm/fsm.h
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
/** @brief Missing transition policy. */
|
||||||
|
enum class missing_transition_policy {
|
||||||
|
/** @brief Missing transition is considered required by validation. */
|
||||||
|
strict,
|
||||||
|
|
||||||
|
/** @brief Missing transition is allowed (event ignored). */
|
||||||
|
ignore
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Marker for undefined transition. */
|
||||||
|
struct no_transition {};
|
||||||
|
|
||||||
|
/** @brief Default guard: always allow. */
|
||||||
|
struct always_allow {
|
||||||
|
template <typename TContext, typename TFrom, typename TEvent>
|
||||||
|
bool operator() (TContext &, const TFrom &, const TEvent &) const noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Default action: do nothing. */
|
||||||
|
struct do_nothing {
|
||||||
|
template <typename TContext, typename TFrom, typename TTo, typename TEvent>
|
||||||
|
void operator() (TContext &, const TFrom &, const TTo &, const TEvent &) const noexcept {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transition descriptor.
|
||||||
|
*
|
||||||
|
* @tparam TTo Destination state type.
|
||||||
|
* @tparam TGuard Guard functor type.
|
||||||
|
* @tparam TAction Action functor type.
|
||||||
|
*/
|
||||||
|
template <typename TTo, typename TGuard = always_allow, typename TAction = do_nothing>
|
||||||
|
struct transition_to {
|
||||||
|
using to_state = TTo;
|
||||||
|
using guard = TGuard;
|
||||||
|
using action = TAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transition table mapping: specialize to define transitions.
|
||||||
|
* Default: no_transition.
|
||||||
|
*/
|
||||||
|
template <typename TFrom, typename TEvent>
|
||||||
|
struct transition {
|
||||||
|
using type = no_transition;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Optional mapping to names for logs. */
|
||||||
|
template <typename T>
|
||||||
|
struct name_of {
|
||||||
|
static constexpr const char *value = "?";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Logger interface (default no-op).
|
||||||
|
*
|
||||||
|
* You can implement:
|
||||||
|
* - ignored_transition(from, event)
|
||||||
|
* - unhandled_transition(from, event) // policy=strict but missing transition at runtime
|
||||||
|
* - guard_blocked(from, event)
|
||||||
|
*/
|
||||||
|
struct null_logger {
|
||||||
|
void ignored_transition (const char *, const char *) noexcept {}
|
||||||
|
void unhandled_transition (const char *, const char *) noexcept {}
|
||||||
|
void guard_blocked (const char *, const char *) noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Hooks: state-only. */
|
||||||
|
template <typename TState>
|
||||||
|
struct on_exit_state {
|
||||||
|
template <typename TContext>
|
||||||
|
static void call (TContext &, const TState &) noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TState>
|
||||||
|
struct on_entry_state {
|
||||||
|
template <typename TContext>
|
||||||
|
static void call (TContext &, const TState &) noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Hooks: event-specific. */
|
||||||
|
template <typename TState, typename TEvent>
|
||||||
|
struct on_exit_event {
|
||||||
|
template <typename TContext>
|
||||||
|
static void call (TContext &, const TState &, const TEvent &) noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TState, typename TEvent>
|
||||||
|
struct on_entry_event {
|
||||||
|
template <typename TContext>
|
||||||
|
static void call (TContext &, const TState &, const TEvent &) noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Factory: construct destination state.
|
||||||
|
*
|
||||||
|
* IMPORTANT: Now includes TContext in template parameters to avoid templated make<TContext>().
|
||||||
|
*
|
||||||
|
* Default: To{}.
|
||||||
|
* Specialize state_factory<TContext, From, Event, To> as needed.
|
||||||
|
*/
|
||||||
|
template <typename TContext, typename TFrom, typename TEvent, typename TTo>
|
||||||
|
struct state_factory {
|
||||||
|
static TTo make (TContext &, const TFrom &, const TEvent &) {
|
||||||
|
return TTo{};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Per (State,Event) missing transition policy.
|
||||||
|
*
|
||||||
|
* Default is ignore. Mark only required pairs strict.
|
||||||
|
*/
|
||||||
|
template <typename TFrom, typename TEvent>
|
||||||
|
struct missing_transition {
|
||||||
|
static constexpr missing_transition_policy policy = missing_transition_policy::ignore;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Helper: is T one of Ts... */
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
struct is_one_of : std::disjunction<std::is_same<T, Ts>...> {};
|
||||||
|
|
||||||
|
/** @brief Helper: transition exists? */
|
||||||
|
template <typename TFrom, typename TEvent>
|
||||||
|
struct has_transition
|
||||||
|
: std::bool_constant < !std::is_same_v<typename transition<TFrom, TEvent>::type, no_transition >> {};
|
||||||
|
|
||||||
|
/** @brief Factory validation (call-form). */
|
||||||
|
template <typename TContext, typename TFrom, typename TEvent, typename TTo, typename = void>
|
||||||
|
struct is_factory_invocable : std::false_type {};
|
||||||
|
|
||||||
|
template <typename TContext, typename TFrom, typename TEvent, typename TTo>
|
||||||
|
struct is_factory_invocable <
|
||||||
|
TContext, TFrom, TEvent, TTo,
|
||||||
|
std::void_t<decltype (state_factory<TContext, TFrom, TEvent, TTo>::make (
|
||||||
|
std::declval<TContext &>(),
|
||||||
|
std::declval<const TFrom &>(),
|
||||||
|
std::declval<const TEvent &>())) >>
|
||||||
|
: std::bool_constant<std::is_same_v<
|
||||||
|
decltype (state_factory<TContext, TFrom, TEvent, TTo>::make (
|
||||||
|
std::declval<TContext &>(),
|
||||||
|
std::declval<const TFrom &>(),
|
||||||
|
std::declval<const TEvent &>())),
|
||||||
|
TTo>> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief FSM with runtime state storage and explicit compile-time validation.
|
||||||
|
*
|
||||||
|
* dispatch():
|
||||||
|
* - never static_asserts on missing transitions (avoids State×Event explosion with std::visit)
|
||||||
|
* - handles missing transitions via missing_transition<State,Event>::policy (ignore/strict)
|
||||||
|
*
|
||||||
|
* validate_events<Ev...>():
|
||||||
|
* - compile-time checks ONLY for pairs marked strict.
|
||||||
|
*/
|
||||||
|
template <typename TContext, typename TLogger, typename... TStates>
|
||||||
|
class fsm {
|
||||||
|
public:
|
||||||
|
using state_variant = std::variant<TStates...>;
|
||||||
|
|
||||||
|
template <typename TInitial,
|
||||||
|
typename = std::enable_if_t<is_one_of<TInitial, TStates...>::value>>
|
||||||
|
explicit fsm (TContext &ctx, TInitial initial, TLogger logger = TLogger{})
|
||||||
|
: m_ctx (ctx)
|
||||||
|
, m_logger (std::move (logger))
|
||||||
|
, m_state (std::move (initial)) {
|
||||||
|
std::visit (init_entry_visitor{*this}, m_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TEvent>
|
||||||
|
bool dispatch (const TEvent &ev) {
|
||||||
|
dispatch_visitor<TEvent> v{*this, ev};
|
||||||
|
return std::visit (v, m_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... TEvents>
|
||||||
|
static constexpr void validate_events() {
|
||||||
|
(validate_one_event<TEvents>(), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
const state_variant &state() const noexcept {
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
state_variant &state() noexcept {
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename TEvent>
|
||||||
|
struct dispatch_visitor {
|
||||||
|
fsm &self;
|
||||||
|
const TEvent &ev;
|
||||||
|
|
||||||
|
template <typename TFrom>
|
||||||
|
bool operator() (TFrom &from) const {
|
||||||
|
return self.template dispatch_from<TFrom> (from, ev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct init_entry_visitor {
|
||||||
|
fsm &self;
|
||||||
|
template <typename TState>
|
||||||
|
void operator() (TState &st) const {
|
||||||
|
on_entry_state<TState>::call (self.m_ctx, st);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TEvent>
|
||||||
|
struct entry_after_commit_visitor {
|
||||||
|
fsm &self;
|
||||||
|
const TEvent &ev;
|
||||||
|
|
||||||
|
template <typename TState>
|
||||||
|
void operator() (TState &st) const {
|
||||||
|
on_entry_state<TState>::call (self.m_ctx, st);
|
||||||
|
on_entry_event<TState, TEvent>::call (self.m_ctx, st, ev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TFrom, typename TEvent>
|
||||||
|
bool dispatch_from (TFrom &from, const TEvent &ev) {
|
||||||
|
using tr = typename transition<TFrom, TEvent>::type;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<tr, no_transition>) {
|
||||||
|
if constexpr (missing_transition<TFrom, TEvent>::policy == missing_transition_policy::strict)
|
||||||
|
m_logger.unhandled_transition (name_of<TFrom>::value, name_of<TEvent>::value);
|
||||||
|
else
|
||||||
|
m_logger.ignored_transition (name_of<TFrom>::value, name_of<TEvent>::value);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
using to_state = typename tr::to_state;
|
||||||
|
using guard_t = typename tr::guard;
|
||||||
|
using action_t = typename tr::action;
|
||||||
|
|
||||||
|
static_assert (is_one_of<to_state, TStates...>::value,
|
||||||
|
"FSM: transition target state not in FSM state list.");
|
||||||
|
|
||||||
|
static_assert (std::is_invocable_r_v<bool, guard_t, TContext &, const TFrom &, const TEvent &>,
|
||||||
|
"FSM: guard must be callable as bool(Context&, const From&, const Event&).");
|
||||||
|
|
||||||
|
static_assert (std::is_invocable_r_v<void, action_t, TContext &, const TFrom &, const to_state &, const TEvent &>,
|
||||||
|
"FSM: action must be callable as void(Context&, const From&, const To&, const Event&).");
|
||||||
|
|
||||||
|
static_assert (is_factory_invocable<TContext, TFrom, TEvent, to_state>::value,
|
||||||
|
"FSM: state_factory<Context,From,Event,To>::make must be callable as "
|
||||||
|
"To make(Context&, const From&, const Event&).");
|
||||||
|
|
||||||
|
guard_t guard{};
|
||||||
|
if (!guard (m_ctx, from, ev)) {
|
||||||
|
m_logger.guard_blocked (name_of<TFrom>::value, name_of<TEvent>::value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_exit_event<TFrom, TEvent>::call (m_ctx, from, ev);
|
||||||
|
on_exit_state<TFrom>::call (m_ctx, from);
|
||||||
|
|
||||||
|
to_state to = state_factory<TContext, TFrom, TEvent, to_state>::make (m_ctx, from, ev);
|
||||||
|
|
||||||
|
action_t action{};
|
||||||
|
action (m_ctx, from, to, ev);
|
||||||
|
|
||||||
|
m_state = std::move (to);
|
||||||
|
|
||||||
|
std::visit (entry_after_commit_visitor<TEvent> {*this, ev}, m_state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TEvent>
|
||||||
|
static constexpr void validate_one_event() {
|
||||||
|
(validate_pair<TStates, TEvent>(), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TState, typename TEvent>
|
||||||
|
static constexpr void validate_pair() {
|
||||||
|
if constexpr (missing_transition<TState, TEvent>::policy == missing_transition_policy::strict) {
|
||||||
|
static_assert (has_transition<TState, TEvent>::value,
|
||||||
|
"FSM validation: required (strict) transition is missing for (State, Event).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TContext &m_ctx;
|
||||||
|
TLogger m_logger;
|
||||||
|
state_variant m_state;
|
||||||
|
};
|
||||||
148
templatefsm/main.cpp
Normal file
148
templatefsm/main.cpp
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#include "fsm.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/** States */
|
||||||
|
struct Idle { };
|
||||||
|
struct Active {
|
||||||
|
std::uint32_t session_id = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Events */
|
||||||
|
struct EvStart {
|
||||||
|
std::uint32_t requested_session = 0;
|
||||||
|
};
|
||||||
|
struct EvStop { };
|
||||||
|
|
||||||
|
/** Context */
|
||||||
|
struct Context {
|
||||||
|
std::uint32_t starts = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Names (optional) */
|
||||||
|
template <> struct name_of<Idle> {
|
||||||
|
static constexpr const char *value = "Idle";
|
||||||
|
};
|
||||||
|
template <> struct name_of<Active> {
|
||||||
|
static constexpr const char *value = "Active";
|
||||||
|
};
|
||||||
|
template <> struct name_of<EvStart> {
|
||||||
|
static constexpr const char *value = "EvStart";
|
||||||
|
};
|
||||||
|
template <> struct name_of<EvStop> {
|
||||||
|
static constexpr const char *value = "EvStop";
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Logger */
|
||||||
|
struct MyLogger {
|
||||||
|
void ignored_transition (const char *from_state, const char *event) noexcept {
|
||||||
|
(void)from_state;
|
||||||
|
(void)event;
|
||||||
|
// printf("FSM ignored: %s + %s\n", from_state, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unhandled_transition (const char *from_state, const char *event) noexcept {
|
||||||
|
(void)from_state;
|
||||||
|
(void)event;
|
||||||
|
// printf("FSM UNHANDLED: %s + %s\n", from_state, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void guard_blocked (const char *from_state, const char *event) noexcept {
|
||||||
|
(void)from_state;
|
||||||
|
(void)event;
|
||||||
|
// printf("FSM guard blocked: %s + %s\n", from_state, event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Guard */
|
||||||
|
struct GuardStartAllowed {
|
||||||
|
bool operator() (Context &ctx, const Idle &, const EvStart &) const noexcept {
|
||||||
|
return ctx.starts < 10U;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Action */
|
||||||
|
struct ActionOnStart {
|
||||||
|
void operator() (Context &ctx, const Idle &, const Active &, const EvStart &) const noexcept {
|
||||||
|
++ctx.starts;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Factory specialization (NOTE: now includes Context in template parameters) */
|
||||||
|
template <>
|
||||||
|
struct state_factory<Context, Idle, EvStart, Active> {
|
||||||
|
static Active make (Context &, const Idle &, const EvStart &ev) {
|
||||||
|
Active a{};
|
||||||
|
a.session_id = ev.requested_session;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Hooks (optional) */
|
||||||
|
template <>
|
||||||
|
struct on_entry_state<Active> {
|
||||||
|
static void call (Context &, const Active &) noexcept {
|
||||||
|
// start timers, etc.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct on_exit_state<Active> {
|
||||||
|
static void call (Context &, const Active &) noexcept {
|
||||||
|
// stop timers, etc.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct on_entry_event<Active, EvStart> {
|
||||||
|
static void call (Context &, const Active &, const EvStart &) noexcept {
|
||||||
|
// entered Active due to EvStart
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Transitions */
|
||||||
|
template <>
|
||||||
|
struct transition<Idle, EvStart> {
|
||||||
|
using type = transition_to<Active, GuardStartAllowed, ActionOnStart>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct transition<Active, EvStop> {
|
||||||
|
using type = transition_to<Idle>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Adult strictness": mark only required pairs strict.
|
||||||
|
* Everything else is ignore by default.
|
||||||
|
*/
|
||||||
|
template <>
|
||||||
|
struct missing_transition<Idle, EvStart> {
|
||||||
|
static constexpr missing_transition_policy policy = missing_transition_policy::strict;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct missing_transition<Active, EvStop> {
|
||||||
|
static constexpr missing_transition_policy policy = missing_transition_policy::strict;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** FSM type */
|
||||||
|
using MyFsm = fsm<Context, MyLogger, Idle, Active>;
|
||||||
|
|
||||||
|
static constexpr void validate_fsm_contract() {
|
||||||
|
MyFsm::validate_events<EvStart, EvStop>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
validate_fsm_contract();
|
||||||
|
|
||||||
|
Context ctx{};
|
||||||
|
MyFsm f (ctx, Idle{}, MyLogger{});
|
||||||
|
|
||||||
|
f.dispatch (EvStart{42}); // Idle -> Active
|
||||||
|
f.dispatch (EvStop{}); // Active -> Idle
|
||||||
|
|
||||||
|
// Not required by strict contract => default ignore (logged via ignored_transition)
|
||||||
|
f.dispatch (EvStop{}); // Idle + EvStop => ignored
|
||||||
|
f.dispatch (EvStart{7}); // (if currently Active) Active + EvStart => ignored
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
10
templatefsm/templatefsm.pro
Normal file
10
templatefsm/templatefsm.pro
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
CONFIG += console c++17
|
||||||
|
CONFIG -= app_bundle
|
||||||
|
CONFIG -= qt
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
main.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
fsm.h
|
||||||
190
typefactory/README.md
Normal file
190
typefactory/README.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# TypeFactory
|
||||||
|
|
||||||
|
A lightweight registry-based factory for creating objects by runtime identifier.
|
||||||
|
|
||||||
|
`TypeFactory` is a small header-only C++ utility for situations where the exact object type
|
||||||
|
must be selected dynamically at runtime using an ID.
|
||||||
|
|
||||||
|
Typical use cases include:
|
||||||
|
|
||||||
|
- protocol command dispatch
|
||||||
|
- vehicle type identification
|
||||||
|
- runtime parser or handler selection
|
||||||
|
- plugin-like object creation
|
||||||
|
- configuration-based object instantiation
|
||||||
|
|
||||||
|
The implementation is intentionally simple and follows the KISS principle.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- header-only implementation
|
||||||
|
- C++17 compatible
|
||||||
|
- compile-time validation of registered types
|
||||||
|
- fast lookup using `std::unordered_map`
|
||||||
|
- optional access to creator function for advanced use
|
||||||
|
- predictable and compact API
|
||||||
|
- suitable for both commercial and open-source projects
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- C++17 or newer
|
||||||
|
- `ClassId` must be hashable (`std::hash<ClassId>` must be available)
|
||||||
|
|
||||||
|
## Basic Idea
|
||||||
|
|
||||||
|
The factory stores a mapping:
|
||||||
|
|
||||||
|
- **runtime ID** -> **creator function**
|
||||||
|
|
||||||
|
A derived type is registered once, then can be created later by its ID.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "typefactory.h"
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
struct Base {
|
||||||
|
virtual ~Base() = default;
|
||||||
|
virtual int get() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Derived1 : Base {
|
||||||
|
explicit Derived1(int start) : m_start(start) {}
|
||||||
|
int get() override {
|
||||||
|
return m_start + 1;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int m_start = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Derived2 : Base {
|
||||||
|
explicit Derived2(int start) : m_start(start) {}
|
||||||
|
int get() override {
|
||||||
|
return m_start + 2;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int m_start = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TypeFactory<std::string, Base, int> factory;
|
||||||
|
|
||||||
|
factory.registerType<Derived1>("one");
|
||||||
|
factory.registerType<Derived2>("two");
|
||||||
|
|
||||||
|
auto d1 = factory.create("one", 10);
|
||||||
|
auto d2 = factory.create("two", 10);
|
||||||
|
|
||||||
|
return d1->get() + d2->get();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
The factory also provides direct access to the creator function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto fn = factory.creator("one");
|
||||||
|
auto obj = fn(10);
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be useful when integrating with dispatch tables or higher-level runtime logic.
|
||||||
|
|
||||||
|
## API Overview
|
||||||
|
|
||||||
|
### `registerType`
|
||||||
|
|
||||||
|
Registers a derived type for a given runtime ID.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
factory.registerType<Derived>("id");
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile-time checks ensure that:
|
||||||
|
|
||||||
|
- `Derived` inherits from `BaseClass`
|
||||||
|
- `Derived` is constructible from `Args...`
|
||||||
|
|
||||||
|
If the same ID is registered again, the previous registration is overwritten.
|
||||||
|
This is intentional: **last registration wins**.
|
||||||
|
|
||||||
|
### `creator`
|
||||||
|
|
||||||
|
Returns the creator function associated with the given ID.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto fn = factory.creator("id");
|
||||||
|
```
|
||||||
|
|
||||||
|
Throws `std::out_of_range` if the ID is not registered.
|
||||||
|
|
||||||
|
### `create`
|
||||||
|
|
||||||
|
Creates an object directly by ID.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto obj = factory.create("id", args...);
|
||||||
|
```
|
||||||
|
|
||||||
|
Throws `std::out_of_range` if the ID is not registered.
|
||||||
|
|
||||||
|
### `contains`
|
||||||
|
|
||||||
|
Checks whether an ID is registered.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (factory.contains("id")) {
|
||||||
|
// safe to create
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design Notes
|
||||||
|
|
||||||
|
- `TypeFactory` is not thread-safe during registration.
|
||||||
|
- Types should normally be registered during initialization.
|
||||||
|
- Lookup and creation are lightweight and predictable.
|
||||||
|
- The class is designed to be reusable as a low-level infrastructure utility.
|
||||||
|
- The registry is stored in a protected `std::unordered_map`, so derived factories can extend the design if needed.
|
||||||
|
|
||||||
|
## Why Not Abstract Factory?
|
||||||
|
|
||||||
|
This utility is not a classic *Abstract Factory* pattern.
|
||||||
|
|
||||||
|
A classic Abstract Factory typically creates **families of related objects**.
|
||||||
|
|
||||||
|
`TypeFactory` is closer to a:
|
||||||
|
|
||||||
|
- registry-based factory
|
||||||
|
- type factory
|
||||||
|
- runtime creator map
|
||||||
|
|
||||||
|
It focuses on selecting one concrete type by runtime ID.
|
||||||
|
|
||||||
|
## Example Use Cases
|
||||||
|
|
||||||
|
### Vehicle Identification
|
||||||
|
|
||||||
|
A controller returns a vehicle ID.
|
||||||
|
The system uses that ID to create the matching parameter or settings object.
|
||||||
|
|
||||||
|
### Protocol Command Handling
|
||||||
|
|
||||||
|
A command ID is received over the network.
|
||||||
|
The system selects and creates the correct handler object for that command.
|
||||||
|
|
||||||
|
### Runtime Format Selection
|
||||||
|
|
||||||
|
A parser or converter is selected dynamically depending on file type, message type, or configuration.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License.
|
||||||
|
|
||||||
|
For source files, it is recommended to use SPDX headers such as:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2025 Dim Himro
|
||||||
|
```
|
||||||
@@ -1,8 +1,42 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include "typefactory.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
cout << "Hello World!" << endl;
|
cout << "Type factory simple example" << endl;
|
||||||
|
|
||||||
|
struct Base {
|
||||||
|
virtual ~Base() = default;
|
||||||
|
virtual int get() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Derived1 : Base {
|
||||||
|
explicit Derived1 (int start) : m_start (start) {}
|
||||||
|
int get() override {
|
||||||
|
return m_start + 1;
|
||||||
|
}
|
||||||
|
int m_start = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Derived2 : Base {
|
||||||
|
explicit Derived2 (int start) : m_start (start) {}
|
||||||
|
int get() override {
|
||||||
|
return m_start + 2;
|
||||||
|
}
|
||||||
|
int m_start = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeFactory<std::string, Base, int> factory;
|
||||||
|
factory.registerType<Derived1> ("one");
|
||||||
|
factory.registerType<Derived2> ("two");
|
||||||
|
|
||||||
|
auto a = factory.create ("one", 10);
|
||||||
|
auto b = factory.creator ("two") (10); // advanced API: get creator function
|
||||||
|
|
||||||
|
std::cout << a->get() << std::endl;
|
||||||
|
std::cout << b->get() << std::endl;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +1,118 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2026 Dim Himro
|
||||||
|
|
||||||
#ifndef TYPEFACTORY_H
|
#ifndef TYPEFACTORY_H
|
||||||
#define TYPEFACTORY_H
|
#define TYPEFACTORY_H
|
||||||
#include <unordered_map>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Registry-based factory for creating objects by runtime identifier.
|
* @brief Registry-based factory for creating objects by runtime identifier.
|
||||||
*
|
*
|
||||||
* Allows registering derived types and instantiating them
|
* Allows registering derived types and instantiating them using a ClassId key.
|
||||||
* using a ClassId key.
|
|
||||||
*
|
*
|
||||||
* @param ClassId - unique class identification class. For example string, or integer id.
|
* Notes:
|
||||||
* @param BaseClass - all objects should have one base class. For example vehicle or parameter
|
* - Not thread-safe. Register types during initialization phase only.
|
||||||
* @param Args - constructor arguments
|
* - Re-registering the same id overwrites the previous entry ("last wins").
|
||||||
|
* - ClassId must be hashable (std::hash<ClassId> specialization must exist).
|
||||||
|
*
|
||||||
|
* @tparam ClassId -Unique class identifier type (e.g. std::string, int, enum).
|
||||||
|
* @tparam BaseClass -Base type for all created objects.
|
||||||
|
* @tparam Arg - Constructor arguments forwarded to registered derived types (by value).
|
||||||
*
|
*
|
||||||
* Usage example:
|
* Usage example:
|
||||||
* @code
|
* @code
|
||||||
* class Base {
|
* #include <string>
|
||||||
* public:
|
|
||||||
* virtual int get() = 0;
|
|
||||||
* }
|
|
||||||
*
|
*
|
||||||
* class Derived1 : public Base {
|
* struct Base {
|
||||||
* public:
|
* virtual ~Base() = default;
|
||||||
* Derived1 (int start) : m_start(start){}
|
* virtual int get() = 0;
|
||||||
* int get() override {
|
* };
|
||||||
* return m_start + 1;
|
|
||||||
* }
|
|
||||||
* private:
|
|
||||||
* int m_start = 0;
|
|
||||||
* }
|
|
||||||
*
|
*
|
||||||
* class Derived2 : public Base {
|
* struct Derived1 : Base {
|
||||||
* public:
|
* explicit Derived1(int start) : m_start(start) {}
|
||||||
* Derived2 (int start) : m_start(start){}
|
* int get() override { return m_start + 1; }
|
||||||
* int get() override {
|
* int m_start = 0;
|
||||||
* return m_start + 2;
|
* };
|
||||||
* }
|
|
||||||
* private:
|
|
||||||
* int m_start = 0;
|
|
||||||
* }
|
|
||||||
* .
|
|
||||||
* .
|
|
||||||
* .
|
|
||||||
* TypeFactory <std::string, Base, int> typefactory;
|
|
||||||
* typefactory.registerType<Derived1>("one");
|
|
||||||
* typefactory.registerType<Derived2>("2");
|
|
||||||
*
|
*
|
||||||
* auto d1 = typefactory.create ("one")(10);
|
* struct Derived2 : Base {
|
||||||
* auto d2 = typefactory.create ("2")(10);
|
* explicit Derived2(int start) : m_start(start) {}
|
||||||
|
* int get() override { return m_start + 2; }
|
||||||
|
* int m_start = 0;
|
||||||
|
* };
|
||||||
*
|
*
|
||||||
* d1->get();
|
* TypeFactory<std::string, Base, int> factory;
|
||||||
* d2->get();
|
* factory.registerType<Derived1>("one");
|
||||||
|
* factory.registerType<Derived2>("two");
|
||||||
|
*
|
||||||
|
* auto a = factory.create("one", 10);
|
||||||
|
* auto b = factory.creator("two")(10); // advanced API: get creator function
|
||||||
|
*
|
||||||
|
* (void)a->get();
|
||||||
|
* (void)b->get();
|
||||||
* @endcode
|
* @endcode
|
||||||
*/
|
*/
|
||||||
template<class ClassId, class BaseClass, class ... Args>
|
template <class ClassId, class BaseClass, class... Args>
|
||||||
class TypeFactory {
|
class TypeFactory {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<BaseClass> BaseClass_SP;
|
using BasePtr = std::shared_ptr<BaseClass>;
|
||||||
/**
|
using CreatorFn = BasePtr (*)(Args...);
|
||||||
* @brief typefactoryFunction - typefactory function pointer type. Generally speaking it is pointer to constructor.
|
|
||||||
*/
|
|
||||||
typedef BaseClass_SP (*typefactoryFunction) (Args ...);
|
|
||||||
|
|
||||||
TypeFactory() = default;
|
TypeFactory() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief registerType - template function to register class in factory
|
* @brief Registers a Derived type under the given id.
|
||||||
* @param Derived - registering type
|
*
|
||||||
* @param id - unique class identification
|
* Requirements (checked at compile-time):
|
||||||
*
|
* - Derived must inherit from BaseClass
|
||||||
* Usage example:
|
* - Derived must be constructible from Args...
|
||||||
* @code
|
*
|
||||||
* typefactory.registerType<Derived1>("one");
|
* Re-registering the same id overwrites the previous entry ("last wins").
|
||||||
* typefactory.registerType<Derived2>("2");
|
*/
|
||||||
* @endcode
|
template <class Derived>
|
||||||
*/
|
void registerType(const ClassId& id) {
|
||||||
template<class Derived>
|
static_assert(std::is_base_of_v<BaseClass, Derived>,
|
||||||
void registerType (const ClassId& id) {
|
"Derived must inherit from BaseClass");
|
||||||
/// Register Derived type by storing its constructor wrapper
|
static_assert(std::is_constructible_v<Derived, Args...>,
|
||||||
|
"Derived must be constructible from Args...");
|
||||||
|
|
||||||
|
classes.insert_or_assign(id, &instantiate<Derived>);
|
||||||
|
}
|
||||||
|
|
||||||
static_assert(std::is_base_of_v<BaseClass, Derived>,
|
/**
|
||||||
"Derived must inherit from BaseClass");
|
* @brief Returns creator function for the given id.
|
||||||
static_assert(std::is_constructible_v<Derived, Args...>,
|
* @throws std::out_of_range when id is not registered.
|
||||||
"Derived must be constructible from Args...");
|
*/
|
||||||
classes[id] = &typefactory<Derived>;
|
[[nodiscard]] CreatorFn creator(const ClassId& id) const {
|
||||||
}
|
return classes.at(id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief create - Returns creator function for id.
|
* @brief Creates an instance for the given id using provided arguments.
|
||||||
* @param id - unique class identification
|
* @throws std::out_of_range when id is not registered.
|
||||||
* @return shared pointer to new class.
|
*/
|
||||||
* @throws std::out_of_range when id not found in map
|
[[nodiscard]] BasePtr create(const ClassId& id, Args... args) const {
|
||||||
*/
|
return creator(id)(std::move(args)...);
|
||||||
typefactoryFunction create (const ClassId& id) const {
|
}
|
||||||
return classes.at (id);
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief classes - Not thread-safe. Register types during initialization phase only.
|
|
||||||
*/
|
|
||||||
std::unordered_map<ClassId, typefactoryFunction> classes;
|
|
||||||
|
|
||||||
private:
|
/**
|
||||||
/**
|
* @brief Checks whether an id is registered.
|
||||||
* @brief typefactory - main functionality, create registered object
|
*/
|
||||||
* @param Derived - class type
|
[[nodiscard]] bool contains(const ClassId& id) const {
|
||||||
* @param args - constructor arguments
|
return classes.find(id) != classes.end();
|
||||||
* @return shared pointer to register Derived class
|
}
|
||||||
*/
|
|
||||||
template<class Derived>
|
protected:
|
||||||
static BaseClass_SP typefactory (Args ... args) {
|
// Protected by design: derived factories may extend/inspect the registry.
|
||||||
return std::make_shared<Derived> (args ...);
|
std::unordered_map<ClassId, CreatorFn> classes;
|
||||||
}
|
|
||||||
|
private:
|
||||||
|
template <class Derived>
|
||||||
|
static BasePtr instantiate(Args... args) {
|
||||||
|
return std::make_shared<Derived>(std::move(args)...);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TYPEFACTORY_H
|
#endif // TYPEFACTORY_H
|
||||||
|
|||||||
Reference in New Issue
Block a user