libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
variant.h
Go to the documentation of this file.
1
9#pragma once
10
11
12#if PROMEKI_ENABLE_CORE
13#include <cstddef>
14#include <cstdint>
15#include <format>
16#include <initializer_list>
17#include <string_view>
18#include <type_traits>
19#include <typeindex>
20#include <typeinfo>
21#include <utility>
22#include <nlohmann/json_fwd.hpp>
23#include <promeki/namespace.h>
24#include <promeki/config.h>
25#include <promeki/string.h>
26#include <promeki/error.h>
27#include <promeki/function.h>
28#include <promeki/sharedptr.h>
29#include <promeki/datatype.h>
30#include <promeki/enum.h>
31#include <promeki/list.h>
32#include <promeki/map.h>
33#include <promeki/pair.h>
34#include <promeki/uniqueptr.h>
35#include <promeki/stringlist.h>
36
37PROMEKI_NAMESPACE_BEGIN
38
39class VariantList;
40class VariantMap;
41class JsonObject;
42
59struct VariantBox {
60 RefCount _promeki_refct;
61 const DataType::Data *typeData = nullptr;
62
64 void *payload();
65
67 const void *payload() const;
68
78 VariantBox *_promeki_clone() const;
79
81 static constexpr size_t payloadOffset(size_t typeAlign) {
82 const size_t headerEnd = sizeof(VariantBox);
83 const size_t alignment = typeAlign == 0 ? 1 : typeAlign;
84 return (headerEnd + alignment - 1) & ~(alignment - 1);
85 }
86
88 static size_t totalSize(const DataType::Data *td) {
89 return payloadOffset(td->align) + td->size;
90 }
91
99 static VariantBox *allocate(const DataType::Data *td, const void *src);
100
110 static void operator delete(void *p, std::size_t sz) noexcept;
111};
112
156class Variant {
157 public:
166 static const char *typeName(DataTypeID id);
167
181 static Variant fromJson(const nlohmann::json &val);
182
200 static Variant readFromStream(DataStream &stream, const DataType &dt);
201
217 static Variant createDefault(const DataType &dt);
218
219 // ------------------------------------------------------------------
220 // Converter registry
221 //
222 // The registry maps a (FromType, ToType) pair to a function that
223 // takes a Variant holding @c FromType and returns a Variant holding
224 // @c ToType. The library populates the builtin cross-product at
225 // first-use; user code registers its own pairs through the public
226 // API below to make @ref get<T>(), @ref convertTo, and
227 // @ref operator== aware of cross-type conversions involving
228 // user-defined @ref DataType alternatives.
229 // ------------------------------------------------------------------
230
241 using ConverterFn = Variant (*)(const Variant &src, Error *err);
242
259 static void registerConverter(DataTypeID from, DataTypeID to, ConverterFn fn);
260
277 template <auto Fn> static void registerConverter();
278
297 static void registerBuiltinConverters();
298
307 static ConverterFn findConverter(DataTypeID from, DataTypeID to);
308
318 Variant() { registerBuiltinDataTypes(); }
319
321 Variant(const Variant &) = default;
322
324 Variant(Variant &&) noexcept = default;
325
327 Variant &operator=(const Variant &) = default;
328
330 Variant &operator=(Variant &&) noexcept = default;
331
333 ~Variant() = default;
334
348 template <typename T,
349 typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, Variant> &&
350 !std::is_same_v<std::decay_t<T>, VariantBox>>>
351 Variant(const T &value) {
352 setValue<T>(value);
353 return;
354 }
355
367 template <typename T,
368 typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, Variant> &&
369 !std::is_same_v<std::decay_t<T>, VariantBox> &&
370 !std::is_lvalue_reference_v<T>>>
371 Variant(T &&value) {
372 setValueMove<std::remove_cv_t<std::remove_reference_t<T>>>(std::move(value));
373 return;
374 }
375
377 bool isValid() const { return _box.isValid(); }
378
389 template <typename T> void set(const T &value) {
390 setValue<T>(value);
391 return;
392 }
393
403 template <typename T,
404 typename = std::enable_if_t<!std::is_lvalue_reference_v<T> &&
405 !std::is_same_v<std::decay_t<T>, Variant>>>
406 void set(T &&value) {
407 setValueMove<std::remove_cv_t<std::remove_reference_t<T>>>(std::move(value));
408 return;
409 }
410
425 template <typename To> To get(Error *err = nullptr) const;
426
441 template <typename T> const T *peek() const noexcept;
442
444 DataTypeID type() const;
445
447 const char *typeName() const { return typeName(type()); }
448
450 DataType dataType() const;
451
466 const void *payloadPtr() const;
467
475 Variant toStandardType() const;
476
490 String toString(Error *err = nullptr) const;
491
511 String format(const String &spec, Error *err = nullptr) const;
512
529 Enum asEnum(Enum::Type enumType, Error *err = nullptr) const;
530
542 bool operator==(const Variant &other) const;
543
545 bool operator!=(const Variant &other) const { return !(*this == other); }
546
561 Variant convertTo(DataTypeID to, Error *err = nullptr) const;
562
563 private:
565 using BoxPtr = SharedPtr<VariantBox>;
566
567 template <typename T> void setValue(const T &value);
568 template <typename T> void setValueMove(T &&value);
569
570 BoxPtr _box;
571};
572
600class VariantList {
601 public:
602 PROMEKI_DATATYPE(VariantList, DataTypeVariantList, 1)
603
604
605 using ItemList = List<Variant>;
607 using Iterator = Variant *;
609 using ConstIterator = const Variant *;
610
611 VariantList();
612 VariantList(std::initializer_list<Variant> il);
613 explicit VariantList(const ItemList &other);
614 explicit VariantList(ItemList &&other);
615
616 VariantList(const VariantList &other);
617 VariantList(VariantList &&other) noexcept;
618 ~VariantList();
619
620 VariantList &operator=(const VariantList &other);
621 VariantList &operator=(VariantList &&other) noexcept;
622
623 size_t size() const;
624 bool isEmpty() const;
625 void clear();
626 void reserve(size_t capacity);
627
628 Variant &operator[](size_t index);
629 const Variant &operator[](size_t index) const;
630 Variant &at(size_t index);
631 const Variant &at(size_t index) const;
632
633 void pushToBack(const Variant &v);
634 void pushToBack(Variant &&v);
635 void popBack();
636
637 Variant *data();
638 const Variant *data() const;
639
640 Iterator begin();
641 Iterator end();
642 ConstIterator begin() const;
643 ConstIterator end() const;
644 ConstIterator cbegin() const;
645 ConstIterator cend() const;
646
647 ItemList &list();
648 const ItemList &list() const;
649
650 bool operator==(const VariantList &other) const;
651 bool operator!=(const VariantList &other) const { return !(*this == other); }
652
653 String toJsonString() const;
654 static VariantList fromJsonString(const String &json, Error *err = nullptr);
655
664 String toString() const { return toJsonString(); }
665
674 static Result<VariantList> fromString(const String &json) {
675 Error e;
676 VariantList v = fromJsonString(json, &e);
677 if (e.isError()) return makeError<VariantList>(e);
678 return makeResult(std::move(v));
679 }
680
690 Error writeToStream(DataStream &s) const;
691
696 template <uint32_t V> static Result<VariantList> readFromStream(DataStream &s);
697
698 private:
699 UniquePtr<ItemList> _list;
700};
701
714class VariantMap {
715 public:
716 PROMEKI_DATATYPE(VariantMap, DataTypeVariantMap, 1)
717
718
719 using EntryMap = Map<String, Variant>;
721 using EntryPair = Pair<String, Variant>;
723 using ForEachFn = Function<void(const String &, const Variant &)>;
724
725 VariantMap();
726 VariantMap(std::initializer_list<EntryPair> il);
727 explicit VariantMap(const EntryMap &other);
728 explicit VariantMap(EntryMap &&other);
729
730 VariantMap(const VariantMap &other);
731 VariantMap(VariantMap &&other) noexcept;
732 ~VariantMap();
733
734 VariantMap &operator=(const VariantMap &other);
735 VariantMap &operator=(VariantMap &&other) noexcept;
736
737 size_t size() const;
738 bool isEmpty() const;
739 void clear();
740 bool contains(const String &key) const;
741
742 void insert(const String &key, const Variant &value);
743 void insert(const String &key, Variant &&value);
744 bool remove(const String &key);
745
746 Variant value(const String &key) const;
747 Variant value(const String &key, const Variant &defaultValue) const;
748
749 Variant *find(const String &key);
750 const Variant *find(const String &key) const;
751
752 StringList keys() const;
753 void forEach(ForEachFn fn) const;
754
755 EntryMap &map();
756 const EntryMap &map() const;
757
758 bool operator==(const VariantMap &other) const;
759 bool operator!=(const VariantMap &other) const { return !(*this == other); }
760
761 String toJsonString() const;
762 static VariantMap fromJsonString(const String &json, Error *err = nullptr);
763
772 String toString() const { return toJsonString(); }
773
782 static Result<VariantMap> fromString(const String &json) {
783 Error e;
784 VariantMap v = fromJsonString(json, &e);
785 if (e.isError()) return makeError<VariantMap>(e);
786 return makeResult(std::move(v));
787 }
788
797 Error writeToStream(DataStream &s) const;
798
803 template <uint32_t V> static Result<VariantMap> readFromStream(DataStream &s);
804
805 private:
806 UniquePtr<EntryMap> _map;
807};
808
809// Inline template definitions for Variant::set / setValue / peek /
810// registerConverter must live in the header so consumer TUs can
811// instantiate them for their own types.
812
813namespace Detail {
814
824SharedPtr<VariantBox> makeVariantBox(const DataType::Data *typeData, const void *value);
825
834SharedPtr<VariantBox> makeVariantBoxMove(const DataType::Data *typeData, void *value);
835
836} // namespace Detail
837
838template <typename T> void Variant::setValue(const T &value) {
839 // Preserve the legacy std::variant-based behaviour where
840 // TypedEnum<X>-derived types slice to Enum on assignment. The
841 // registry only carries the Enum entry, so derived types route
842 // through it explicitly rather than producing an invalid
843 // Variant.
844 if constexpr (std::is_base_of_v<Enum, T> && !std::is_same_v<T, Enum>) {
845 const Enum &sliced = static_cast<const Enum &>(value);
846 _box = Detail::makeVariantBox(DataType::of<Enum>().data(), &sliced);
847 return;
848 } else {
849 const DataType dt = DataType::of<T>();
850 if (dt.isValid()) {
851 _box = Detail::makeVariantBox(dt.data(), &value);
852 return;
853 }
854 // Fallback: replicate legacy std::variant overload
855 // resolution for the common case where @p T is
856 // convertible to one of the registered types but is
857 // not itself registered — string literals, char
858 // arrays, @c std::string, etc. String is the
859 // dominant "implicit landing" alternative.
860 if constexpr (!std::is_same_v<T, String> &&
861 std::is_constructible_v<String, const T &>) {
862 String s(value);
863 _box = Detail::makeVariantBox(DataType::of<String>().data(), &s);
864 return;
865 }
866 _box.clear();
867 return;
868 }
869}
870
871template <typename T> void Variant::setValueMove(T &&value) {
872 // T is already a value type by the time this is instantiated
873 // (the public set / converting ctor strip references), so the
874 // typedef below is just for symmetry with @ref setValue. The
875 // Enum slicing path mirrors the copy variant: TypedEnum<X> →
876 // Enum payload via the registry's Enum entry.
877 using U = std::remove_cv_t<std::remove_reference_t<T>>;
878 if constexpr (std::is_base_of_v<Enum, U> && !std::is_same_v<U, Enum>) {
879 Enum sliced = static_cast<Enum &&>(value);
880 _box = Detail::makeVariantBoxMove(DataType::of<Enum>().data(), &sliced);
881 return;
882 } else {
883 const DataType dt = DataType::of<U>();
884 if (dt.isValid()) {
885 U tmp(std::move(value));
886 _box = Detail::makeVariantBoxMove(dt.data(), &tmp);
887 return;
888 }
889 // Same String-convertible fallback as the copy path.
890 if constexpr (!std::is_same_v<U, String> &&
891 std::is_constructible_v<String, U &&>) {
892 String s(std::move(value));
893 _box = Detail::makeVariantBoxMove(DataType::of<String>().data(), &s);
894 return;
895 }
896 _box.clear();
897 return;
898 }
899}
900
901template <typename T> const T *Variant::peek() const noexcept {
902 if (_box.isNull()) return nullptr;
903 const DataType::Data *td = _box->typeData;
904 if (td == nullptr) return nullptr;
905 if (td->cppType != std::type_index(typeid(T))) return nullptr;
906 return static_cast<const T *>(_box->payload());
907}
908
909template <typename To> To Variant::get(Error *err) const {
910 if (err != nullptr) *err = Error::Ok;
911 if (const To *direct = peek<To>()) return *direct;
912
913 const DataType targetDt = DataType::of<To>();
914 if (!targetDt.isValid()) {
915 if (err != nullptr) *err = Error::Invalid;
916 return To{};
917 }
918 Error convErr;
919 Variant out = convertTo(targetDt.id(), &convErr);
920 if (convErr.isError()) {
921 if (err != nullptr) *err = convErr;
922 return To{};
923 }
924 if (const To *converted = out.peek<To>()) return *converted;
925 if (err != nullptr) *err = Error::Invalid;
926 return To{};
927}
928
929namespace Detail {
930
939template <typename Fn> struct ConverterFnTraits;
940
941template <typename To, typename From> struct ConverterFnTraits<To (*)(const From &, Error *)> {
942 using FromType = From;
943 using ToType = To;
944};
945
946} // namespace Detail
947
948template <auto Fn> void Variant::registerConverter() {
949 using Traits = Detail::ConverterFnTraits<decltype(Fn)>;
950 using From = typename Traits::FromType;
951 using To = typename Traits::ToType;
952 registerConverter(DataType::of<From>().id(), DataType::of<To>().id(),
953 +[](const Variant &v, Error *err) -> Variant {
954 const From *p = v.peek<From>();
955 if (p == nullptr) {
956 if (err != nullptr) *err = Error::Invalid;
957 return Variant();
958 }
959 return Variant(Fn(*p, err));
960 });
961}
962
963namespace Detail {
964
974template <typename T>
975inline Variant variantConvertStringTo(const Variant &src, Error *err) {
976 if (err != nullptr) *err = Error::Ok;
977 const String *s = src.peek<String>();
978 if (s == nullptr) {
979 if (err != nullptr) *err = Error::Invalid;
980 return Variant();
981 }
982 const DataType dt = DataType::of<T>();
983 const DataType::Data *td = dt.data();
984 if (td == nullptr || td->ops.fromString == nullptr) {
985 if (err != nullptr) *err = Error::Invalid;
986 return Variant();
987 }
988 T tmp{};
989 Error e;
990 if (!td->ops.fromString(*s, &tmp, &e)) {
991 if (err != nullptr) *err = e.isError() ? e : Error::Invalid;
992 return Variant();
993 }
994 return Variant(std::move(tmp));
995}
996
1004template <typename T>
1005inline Variant variantConvertToString(const Variant &src, Error *err) {
1006 if (err != nullptr) *err = Error::Ok;
1007 const T *p = src.peek<T>();
1008 if (p == nullptr) {
1009 if (err != nullptr) *err = Error::Invalid;
1010 return Variant();
1011 }
1012 const DataType dt = DataType::of<T>();
1013 const DataType::Data *td = dt.data();
1014 if (td == nullptr || td->ops.toString == nullptr) {
1015 if (err != nullptr) *err = Error::Invalid;
1016 return Variant();
1017 }
1018 Error e;
1019 String result = td->ops.toString(p, &e);
1020 if (e.isError()) {
1021 if (err != nullptr) *err = e;
1022 return Variant();
1023 }
1024 return Variant(std::move(result));
1025}
1026
1036template <typename Integer, typename T>
1037inline Variant variantConvertIntegerTo(const Variant &src, Error *err) {
1038 if (err != nullptr) *err = Error::Ok;
1039 const Integer *p = src.peek<Integer>();
1040 if (p == nullptr) {
1041 if (err != nullptr) *err = Error::Invalid;
1042 return Variant();
1043 }
1044 const DataType dt = DataType::of<T>();
1045 const DataType::Data *td = dt.data();
1046 if (td == nullptr || td->ops.fromInt == nullptr) {
1047 if (err != nullptr) *err = Error::Invalid;
1048 return Variant();
1049 }
1050 T tmp{};
1051 Error e;
1052 if (!td->ops.fromInt(static_cast<int64_t>(*p), &tmp, &e)) {
1053 if (err != nullptr) *err = e.isError() ? e : Error::Invalid;
1054 return Variant();
1055 }
1056 return Variant(std::move(tmp));
1057}
1058
1066template <typename T, typename Integer>
1067inline Variant variantConvertToInteger(const Variant &src, Error *err) {
1068 if (err != nullptr) *err = Error::Ok;
1069 const T *p = src.peek<T>();
1070 if (p == nullptr) {
1071 if (err != nullptr) *err = Error::Invalid;
1072 return Variant();
1073 }
1074 const DataType dt = DataType::of<T>();
1075 const DataType::Data *td = dt.data();
1076 if (td == nullptr || td->ops.toInt == nullptr) {
1077 if (err != nullptr) *err = Error::Invalid;
1078 return Variant();
1079 }
1080 Error e;
1081 int64_t raw = td->ops.toInt(p, &e);
1082 if (e.isError()) {
1083 if (err != nullptr) *err = e;
1084 return Variant();
1085 }
1086 return Variant(static_cast<Integer>(raw));
1087}
1088
1096template <typename Float, typename T>
1097inline Variant variantConvertFloatTo(const Variant &src, Error *err) {
1098 if (err != nullptr) *err = Error::Ok;
1099 const Float *p = src.peek<Float>();
1100 if (p == nullptr) {
1101 if (err != nullptr) *err = Error::Invalid;
1102 return Variant();
1103 }
1104 const DataType dt = DataType::of<T>();
1105 const DataType::Data *td = dt.data();
1106 if (td == nullptr || td->ops.fromFloat == nullptr) {
1107 if (err != nullptr) *err = Error::Invalid;
1108 return Variant();
1109 }
1110 T tmp{};
1111 Error e;
1112 if (!td->ops.fromFloat(static_cast<double>(*p), &tmp, &e)) {
1113 if (err != nullptr) *err = e.isError() ? e : Error::Invalid;
1114 return Variant();
1115 }
1116 return Variant(std::move(tmp));
1117}
1118
1126template <typename T, typename Float>
1127inline Variant variantConvertToFloat(const Variant &src, Error *err) {
1128 if (err != nullptr) *err = Error::Ok;
1129 const T *p = src.peek<T>();
1130 if (p == nullptr) {
1131 if (err != nullptr) *err = Error::Invalid;
1132 return Variant();
1133 }
1134 const DataType dt = DataType::of<T>();
1135 const DataType::Data *td = dt.data();
1136 if (td == nullptr || td->ops.toFloat == nullptr) {
1137 if (err != nullptr) *err = Error::Invalid;
1138 return Variant();
1139 }
1140 Error e;
1141 double raw = td->ops.toFloat(p, &e);
1142 if (e.isError()) {
1143 if (err != nullptr) *err = e;
1144 return Variant();
1145 }
1146 return Variant(static_cast<Float>(raw));
1147}
1148
1156template <typename T>
1157inline Variant variantConvertJsonObjectTo(const Variant &src, Error *err) {
1158 if (err != nullptr) *err = Error::Ok;
1159 const DataType jsonDt = DataType::byCppType(std::type_index(typeid(JsonObject)));
1160 if (!jsonDt.isValid()) {
1161 if (err != nullptr) *err = Error::Invalid;
1162 return Variant();
1163 }
1164 const void *raw = src.payloadPtr();
1165 if (raw == nullptr || src.type() != jsonDt.id()) {
1166 if (err != nullptr) *err = Error::Invalid;
1167 return Variant();
1168 }
1169 const JsonObject *p = static_cast<const JsonObject *>(raw);
1170 const DataType dt = DataType::of<T>();
1171 const DataType::Data *td = dt.data();
1172 if (td == nullptr || td->ops.fromJson == nullptr) {
1173 if (err != nullptr) *err = Error::Invalid;
1174 return Variant();
1175 }
1176 T tmp{};
1177 Error e;
1178 if (!td->ops.fromJson(*p, &tmp, &e)) {
1179 if (err != nullptr) *err = e.isError() ? e : Error::Invalid;
1180 return Variant();
1181 }
1182 return Variant(std::move(tmp));
1183}
1184
1192template <typename T>
1193inline Variant variantConvertToJsonObject(const Variant &src, Error *err) {
1194 if (err != nullptr) *err = Error::Ok;
1195 const T *p = src.peek<T>();
1196 if (p == nullptr) {
1197 if (err != nullptr) *err = Error::Invalid;
1198 return Variant();
1199 }
1200 const DataType dt = DataType::of<T>();
1201 const DataType::Data *td = dt.data();
1202 if (td == nullptr || td->ops.toJson == nullptr) {
1203 if (err != nullptr) *err = Error::Invalid;
1204 return Variant();
1205 }
1206 Error e;
1207 JsonObject result = td->ops.toJson(p, &e);
1208 if (e.isError()) {
1209 if (err != nullptr) *err = e;
1210 return Variant();
1211 }
1212 return Variant(std::move(result));
1213}
1214
1215template <typename T> inline void registerAutoConverters(const DataType &dt) {
1216 if (!dt.isValid()) return;
1217 // String is a builtin DataType and must be registered before any
1218 // type's auto-converters are wired up — registerBuiltinDataTypes
1219 // installs it first. Skip silently when called outside that
1220 // sequence (e.g. on a corrupted registry).
1221 const DataType stringDt = DataType::byCppType(std::type_index(typeid(String)));
1222 if (!stringDt.isValid()) return;
1223 if (dt == stringDt) return;
1224 if (dt.ops().fromString != nullptr) {
1225 Variant::registerConverter(stringDt.id(), dt.id(), &variantConvertStringTo<T>);
1226 }
1227 if (dt.ops().toString != nullptr) {
1228 Variant::registerConverter(dt.id(), stringDt.id(), &variantConvertToString<T>);
1229 }
1230 // JsonObject <-> T auto-wiring. Only the types that populated
1231 // ops.toJson / ops.fromJson get a converter; everyone else
1232 // continues to round-trip through String like before.
1233 const DataType jsonDt = DataType::byCppType(std::type_index(typeid(JsonObject)));
1234 if (jsonDt.isValid() && dt != jsonDt) {
1235 if (dt.ops().fromJson != nullptr) {
1236 Variant::registerConverter(jsonDt.id(), dt.id(),
1237 &variantConvertJsonObjectTo<T>);
1238 }
1239 if (dt.ops().toJson != nullptr) {
1240 Variant::registerConverter(dt.id(), jsonDt.id(),
1241 &variantConvertToJsonObject<T>);
1242 }
1243 }
1244 // Integer <-> T auto-wiring. Each (Integer, T) pair is
1245 // registered against the live converter registry only when the
1246 // type's ops table populates the matching toInt / fromInt
1247 // slot. TypeRegistry wrappers, FrameNumber, FrameCount and
1248 // Enum participate; Enum populates only the toInt direction.
1249 auto wireIntPair = [&dt]<typename Integer>() {
1250 const DataType intDt = DataType::byCppType(std::type_index(typeid(Integer)));
1251 if (!intDt.isValid()) return;
1252 if (dt.ops().fromInt != nullptr) {
1253 Variant::registerConverter(intDt.id(), dt.id(),
1254 &variantConvertIntegerTo<Integer, T>);
1255 }
1256 if (dt.ops().toInt != nullptr) {
1257 Variant::registerConverter(dt.id(), intDt.id(),
1258 &variantConvertToInteger<T, Integer>);
1259 }
1260 };
1261 if (dt.ops().toInt != nullptr || dt.ops().fromInt != nullptr) {
1262 wireIntPair.template operator()<bool>();
1263 wireIntPair.template operator()<uint8_t>();
1264 wireIntPair.template operator()<int8_t>();
1265 wireIntPair.template operator()<uint16_t>();
1266 wireIntPair.template operator()<int16_t>();
1267 wireIntPair.template operator()<uint32_t>();
1268 wireIntPair.template operator()<int32_t>();
1269 wireIntPair.template operator()<uint64_t>();
1270 wireIntPair.template operator()<int64_t>();
1271 }
1272 // Float <-> T auto-wiring mirrors the integer pattern above;
1273 // the ops slot carries double (the widest float) and the
1274 // narrowing happens in @ref variantConvertToFloat / promoting
1275 // happens in @ref variantConvertFloatTo.
1276 auto wireFloatPair = [&dt]<typename Float>() {
1277 const DataType floatDt = DataType::byCppType(std::type_index(typeid(Float)));
1278 if (!floatDt.isValid()) return;
1279 if (dt.ops().fromFloat != nullptr) {
1280 Variant::registerConverter(floatDt.id(), dt.id(),
1281 &variantConvertFloatTo<Float, T>);
1282 }
1283 if (dt.ops().toFloat != nullptr) {
1284 Variant::registerConverter(dt.id(), floatDt.id(),
1285 &variantConvertToFloat<T, Float>);
1286 }
1287 };
1288 if (dt.ops().toFloat != nullptr || dt.ops().fromFloat != nullptr) {
1289 wireFloatPair.template operator()<float>();
1290 wireFloatPair.template operator()<double>();
1291 }
1292 return;
1293}
1294
1295} // namespace Detail
1296
1314Variant promekiResolveVariantPath(const Variant &root, const String &path, Error *err = nullptr);
1315
1316PROMEKI_NAMESPACE_END
1317
1326template <> struct std::formatter<promeki::Variant> : std::formatter<std::string_view> {
1327 using Base = std::formatter<std::string_view>;
1328 template <typename FormatContext>
1329 auto format(const promeki::Variant &v, FormatContext &ctx) const {
1330 promeki::String s = v.get<promeki::String>();
1331 return Base::format(std::string_view(s.cstr(), s.byteCount()), ctx);
1332 }
1333};
1334
1335#endif // PROMEKI_ENABLE_CORE