Parameters: reorganise type conversion dependencies
the generic cross-type assignment and -conversion will also be used by the `par::Scale` to join an automation evaluation result with a base value...
This commit is contained in:
parent
0cbe9d7dd6
commit
a1b80c22df
3 changed files with 20 additions and 216 deletions
|
|
@ -35,11 +35,9 @@
|
|||
|
||||
|
||||
#include "lib/par/spec.hpp"
|
||||
#include "lib/meta/typelist.hpp"
|
||||
#include "lib/meta/generator.hpp"
|
||||
#include "lib/par/provision.hpp"
|
||||
#include "lib/meta/generator.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
|
|
|||
|
|
@ -36,21 +36,13 @@
|
|||
|
||||
|
||||
#include "lib/par/domain.hpp"
|
||||
#include "lib/par/type-handler.hpp"
|
||||
#include "lib/meta/generator.hpp"
|
||||
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace par {
|
||||
|
||||
using util::isNeg;
|
||||
using std::same_as;
|
||||
using std::floating_point;
|
||||
using std::numeric_limits;
|
||||
|
||||
|
||||
// GCC > 13 warns at class definition when a new overload shadows an inherited virtual function.
|
||||
// While theoretically correct, in practice any call will be dispatched through the base interface,
|
||||
// which in turn is also generated by metaprogramming from a type list.
|
||||
|
|
@ -86,103 +78,10 @@ _Pragma("GCC diagnostic ignored \"-Woverloaded-virtual\"")
|
|||
};
|
||||
|
||||
|
||||
/* ===== type conversion details ===== */
|
||||
|
||||
/** Constraint: type \a SUB can not represent the full domain of type \a BAS
|
||||
* @todo implicitly also implements a limitation to »numeric« types,
|
||||
* due to the use of std::numeric_limits
|
||||
*/
|
||||
template<typename SUB, typename BAS>
|
||||
concept sub_domain = std::signed_integral<SUB> != std::signed_integral<BAS>
|
||||
or std::numeric_limits<SUB>::max() < std::numeric_limits<BAS>::max()
|
||||
or std::numeric_limits<SUB>::lowest() > std::numeric_limits<BAS>::lowest()
|
||||
;
|
||||
|
||||
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
|
||||
// the following functions are specifically crafted to work around
|
||||
// the ambiguities caused by automatic signed-to-unsigned promotion;
|
||||
// furthermore, the automatic promotion allows to perform the comparison.
|
||||
|
||||
template<typename X, typename Y>
|
||||
inline constexpr bool
|
||||
isLower_safe (X const& x, Y const& y)
|
||||
{
|
||||
return isNeg(x) == isNeg(y)? x < y
|
||||
: isNeg(x);
|
||||
}
|
||||
|
||||
template<typename X, typename Y>
|
||||
inline constexpr bool
|
||||
isLarger_safe (X const& x, Y const& y)
|
||||
{
|
||||
return isNeg(x) == isNeg(y)? x > y
|
||||
: isNeg(y);
|
||||
}
|
||||
_Pragma("GCC diagnostic pop")
|
||||
|
||||
|
||||
/** generic helper to conform into a common value domain
|
||||
* @tparam SUB the implied target value domain
|
||||
* @tparam BAS source data type
|
||||
* @return a value in source data type,
|
||||
* yet conformed into the target data range
|
||||
*/
|
||||
template<typename SUB, typename BAS>
|
||||
inline constexpr BAS
|
||||
preClamp (BAS const& value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
/** special case: clamp the source value into the target value domain.
|
||||
* @remark tricky due to automatic conversions between signed/unsigned */
|
||||
template<typename SUB, typename BAS> requires (sub_domain<SUB,BAS>
|
||||
and not same_as<SUB,bool>)
|
||||
inline constexpr BAS
|
||||
preClamp (BAS const& rawVal)
|
||||
{
|
||||
static_assert (std::is_constructible_v<BAS,SUB const&>
|
||||
,"Value domains must overlap at least partially "
|
||||
"and values must be cross-constructible within overlap");
|
||||
|
||||
auto upperBound = numeric_limits<SUB>::max();
|
||||
auto lowerBound = numeric_limits<SUB>::lowest();
|
||||
|
||||
return isLarger_safe(rawVal, upperBound)? BAS(upperBound)
|
||||
: isLower_safe (rawVal, lowerBound)? BAS(lowerBound)
|
||||
: rawVal;
|
||||
}
|
||||
|
||||
/** special case: trigger a bool at the 0.5 level */
|
||||
template<same_as<bool> B, floating_point F>
|
||||
inline constexpr F
|
||||
preClamp (F const& rawVal)
|
||||
{
|
||||
return 0.5 < rawVal? F{1} : F{0};
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<typename V, typename X>
|
||||
inline void
|
||||
assignConverted (V& targetVal, X const& srcVal)
|
||||
{
|
||||
if constexpr (std::is_assignable_v<V&, X const&>)
|
||||
{
|
||||
targetVal = preClamp<V> (srcVal);
|
||||
}
|
||||
else
|
||||
if constexpr (std::is_constructible_v<V, X const&>)
|
||||
{
|
||||
targetVal.~U();
|
||||
new(&targetVal) V (preClamp<V> (srcVal));
|
||||
}
|
||||
else
|
||||
static_assert (!sizeof(X), "this type conversion is not supported");
|
||||
}
|
||||
|
||||
/* ===== type conversion virtual dispatch ===== */
|
||||
|
||||
template<typename VAL, class IFA>
|
||||
template<typename X, class PAR>
|
||||
|
|
|
|||
|
|
@ -14,18 +14,12 @@
|
|||
|
||||
/** @file type-handler.hpp
|
||||
** Generic building blocks to support parameter value conversions and adaptation.
|
||||
** The implementation of parameters is built up in layers, starting from the generic
|
||||
** interfaces \ref Provision, which describes the fundamental capabilities of a
|
||||
** parameter as conceptualised here) and \ref Domain to support type conversions.
|
||||
**
|
||||
** All extensions beyond this point can be considered implementation-level services.
|
||||
** The BaseDomain layer provides the actual type conversion services, based on internal
|
||||
** knowledge of the concrete data type handled by this parameter.
|
||||
**
|
||||
** A \ref TypedEntity descriptor adds storage to capture type properties and allow
|
||||
** for an ID registration. In addition, the concrete value type is tagged, marking
|
||||
** the allowed value range, clipping behaviour and specifics of the scale (discrete,
|
||||
** stepping, continuous, logarithmic...).
|
||||
** The implementation of parameters is built such as to conceal the actual type of
|
||||
** the parameter values -- which requires a virtual dispatch at some point. Yet the
|
||||
** underlying type conversion functionality can be defined in a generic way, based
|
||||
** on the actual types involved. Simple value-like parameters are based on a fixed
|
||||
** collection of number types, both integral and floating-point. Thus the actual
|
||||
** conversion is handled by the promotion- and coercion rules of C++ in most cases.
|
||||
**
|
||||
** @todo WIP-WIP-WIP can be considered an initial draft and placeholder, as of 2026
|
||||
*/
|
||||
|
|
@ -37,8 +31,10 @@
|
|||
|
||||
#include "lib/par/spec.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/par/domain.hpp"
|
||||
#include "lib/meta/generator.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
|
@ -47,54 +43,20 @@ namespace par {
|
|||
using util::isNeg;
|
||||
using std::same_as;
|
||||
using std::floating_point;
|
||||
using std::signed_integral;
|
||||
using std::numeric_limits;
|
||||
|
||||
|
||||
// GCC > 13 warns at class definition when a new overload shadows an inherited virtual function.
|
||||
// While theoretically correct, in practice any call will be dispatched through the base interface,
|
||||
// which in turn is also generated by metaprogramming from a type list.
|
||||
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109740
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Woverloaded-virtual\"")
|
||||
|
||||
|
||||
template<typename VAL, class IFA>
|
||||
struct DomainSetup
|
||||
{
|
||||
|
||||
template<typename X, class PAR>
|
||||
class TypeHandlerImpl
|
||||
: public PAR
|
||||
{
|
||||
void extractAs (X& targetVal, ValBuff const& valBuff) const override;
|
||||
void conform (ValBuff& targetBuff, X const& srcVal) override;
|
||||
};
|
||||
|
||||
using TypeHandlerChain = meta::InstantiateChained<BaseTypes::List, TypeHandlerImpl, IFA>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename VAL, class IFA>
|
||||
class BaseDomain
|
||||
: public DomainSetup<VAL, IFA>::TypeHandlerChain
|
||||
{
|
||||
public:
|
||||
void applyLimit (ValBuff&) override;
|
||||
void transferTo (ValBuff const& source, Domain& targetDomain, ValBuff& target) const override;
|
||||
};
|
||||
|
||||
|
||||
/* ===== type conversion details ===== */
|
||||
/* ===== type conversion implementation details ===== */
|
||||
|
||||
/** Constraint: type \a SUB can not represent the full domain of type \a BAS
|
||||
* @todo implicitly also implements a limitation to »numeric« types,
|
||||
* due to the use of std::numeric_limits
|
||||
*/
|
||||
template<typename SUB, typename BAS>
|
||||
concept sub_domain = std::signed_integral<SUB> != std::signed_integral<BAS>
|
||||
or std::numeric_limits<SUB>::max() < std::numeric_limits<BAS>::max()
|
||||
or std::numeric_limits<SUB>::lowest() > std::numeric_limits<BAS>::lowest()
|
||||
concept sub_domain = signed_integral<SUB> != signed_integral<BAS>
|
||||
or numeric_limits<SUB>::max() < numeric_limits<BAS>::max()
|
||||
or numeric_limits<SUB>::lowest() > numeric_limits<BAS>::lowest()
|
||||
;
|
||||
|
||||
|
||||
|
|
@ -122,6 +84,7 @@ _Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
|
|||
_Pragma("GCC diagnostic pop")
|
||||
|
||||
|
||||
|
||||
/** generic helper to conform into a common value domain
|
||||
* @tparam SUB the implied target value domain
|
||||
* @tparam BAS source data type
|
||||
|
|
@ -175,7 +138,7 @@ _Pragma("GCC diagnostic pop")
|
|||
else
|
||||
if constexpr (std::is_constructible_v<V, X const&>)
|
||||
{
|
||||
targetVal.~U();
|
||||
targetVal.~V();
|
||||
new(&targetVal) V (preClamp<V> (srcVal));
|
||||
}
|
||||
else
|
||||
|
|
@ -183,62 +146,6 @@ _Pragma("GCC diagnostic pop")
|
|||
}
|
||||
|
||||
|
||||
template<typename VAL, class IFA>
|
||||
template<typename X, class PAR>
|
||||
inline void
|
||||
DomainSetup<VAL,IFA>::TypeHandlerImpl<X,PAR>::extractAs (X& targetVal, ValBuff const& valBuff) const
|
||||
{
|
||||
VAL const& srcVal = asValue<VAL> (valBuff);
|
||||
assignConverted (targetVal, srcVal);
|
||||
}
|
||||
|
||||
|
||||
template<typename VAL, class IFA>
|
||||
template<typename X, class PAR>
|
||||
inline void
|
||||
DomainSetup<VAL,IFA>::TypeHandlerImpl<X,PAR>::conform (ValBuff& targetBuff, X const& srcVal)
|
||||
{
|
||||
VAL& targetVal = asValue<VAL> (targetBuff);
|
||||
assignConverted (targetVal, srcVal);
|
||||
}
|
||||
|
||||
|
||||
template<typename VAL, class IFA>
|
||||
inline void
|
||||
BaseDomain<VAL,IFA>::applyLimit (ValBuff& valBuff)
|
||||
{
|
||||
VAL& val{asValue<VAL> (valBuff)};
|
||||
val = preClamp<VAL> (val);
|
||||
}
|
||||
|
||||
|
||||
template<typename VAL, class IFA>
|
||||
inline void
|
||||
BaseDomain<VAL,IFA>::transferTo (ValBuff const& source, Domain& targetDomain, ValBuff& target) const
|
||||
{
|
||||
targetDomain.conform (target, asValue<VAL> (source));
|
||||
targetDomain.applyLimit (target);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Service-Interface: represent a specific configuration of parameter value type,
|
||||
* range and scale. Every ParamType descriptor is also a _prototype_ and has
|
||||
* an unique identity, ensured by registration, so that it acts as a type identifier.
|
||||
* @todo RLY? registration??
|
||||
* Furthermore, the interface exposes allowed conversion and conforming operations.
|
||||
*/
|
||||
template<typename VAL, class IFA>
|
||||
class TypedEntity
|
||||
: public BaseDomain<VAL,IFA>
|
||||
{
|
||||
public:
|
||||
};
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::par
|
||||
#endif /*LIB_PAR_TYPE_HANDLER_H*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue