574 lines
22 KiB
C++
574 lines
22 KiB
C++
#pragma once
|
|
|
|
|
|
/****************************************************************************************
|
|
* *
|
|
* MOUNT CONTROL COMPONENTS LIBRARY *
|
|
* *
|
|
* *
|
|
* IMPLEMENTATION OF KEY-VALUE PAIRS HOLDER *
|
|
* *
|
|
****************************************************************************************/
|
|
|
|
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
#include "mcc_deserializer.h"
|
|
#include "mcc_serializer.h"
|
|
#include "mcc_utils.h"
|
|
|
|
namespace mcc::impl
|
|
{
|
|
|
|
enum class MccKeyValueHolderErrorCode : int {
|
|
ERROR_OK,
|
|
ERROR_INVALID_KEY,
|
|
ERROR_INCOMPATIBLE_TYPE,
|
|
ERROR_DESERIAL,
|
|
ERROR_SERIAL
|
|
};
|
|
|
|
} // namespace mcc::impl
|
|
|
|
|
|
namespace std
|
|
{
|
|
|
|
template <>
|
|
class is_error_code_enum<mcc::impl::MccKeyValueHolderErrorCode> : public true_type
|
|
{
|
|
};
|
|
|
|
} // namespace std
|
|
|
|
namespace mcc::impl
|
|
{
|
|
|
|
// error category
|
|
struct MccKeyValueHolderCategory : public std::error_category {
|
|
MccKeyValueHolderCategory() : std::error_category() {}
|
|
|
|
const char* name() const noexcept override
|
|
{
|
|
return "MCC-KEYVALUEHOLDER-ERR-CATEGORY";
|
|
}
|
|
|
|
std::string message(int ec) const override
|
|
{
|
|
MccKeyValueHolderErrorCode err = static_cast<MccKeyValueHolderErrorCode>(ec);
|
|
|
|
switch (err) {
|
|
case MccKeyValueHolderErrorCode::ERROR_OK:
|
|
return "OK";
|
|
case MccKeyValueHolderErrorCode::ERROR_INVALID_KEY:
|
|
return "invalid key value";
|
|
case MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE:
|
|
return "incompatible type";
|
|
case MccKeyValueHolderErrorCode::ERROR_DESERIAL:
|
|
return "deserialization error";
|
|
case MccKeyValueHolderErrorCode::ERROR_SERIAL:
|
|
return "serialization error";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static const MccKeyValueHolderCategory& get()
|
|
{
|
|
static const MccKeyValueHolderCategory constInst;
|
|
return constInst;
|
|
}
|
|
};
|
|
|
|
|
|
inline std::error_code make_error_code(MccKeyValueHolderErrorCode ec)
|
|
{
|
|
return std::error_code(static_cast<int>(ec), MccKeyValueHolderCategory::get());
|
|
}
|
|
|
|
|
|
// to follow std::variant requirements (not references, not array, not void)
|
|
template <typename T>
|
|
concept mcc_record_value_c = requires { !std::is_array_v<T> && !std::is_void_v<T> && !std::is_reference_v<T>; };
|
|
|
|
|
|
template <typename T>
|
|
concept mcc_keyvalue_record_c = requires(T t) {
|
|
requires std::same_as<decltype(t.key), const std::string_view>; // key name is immutable!!!
|
|
requires mcc_record_value_c<decltype(t.value)>; // value is mutable
|
|
requires mcc_record_value_c<decltype(t.default_value)>; // default value is mutable
|
|
requires mcc_serialization_params_c<decltype(t.serial_pars)>; // serialization parameters
|
|
};
|
|
|
|
|
|
// must be a std::tuple of mcc_keyvalue_record_c
|
|
template <typename T>
|
|
concept mcc_keyvalue_desc_c = requires(T t) { []<mcc_keyvalue_record_c... Ts>(std::tuple<Ts...>) {}(t); };
|
|
|
|
|
|
static constexpr char MCC_KV_COMMENT_SEQ_ARR[] = "#";
|
|
static constexpr char MCC_KV_KEY_VALUE_DELIM_SEQ_ARR[] = "=";
|
|
static constexpr char MCC_KV_COMPOSITE_VALUE_DELIM_SEQ_ARR[] = ",";
|
|
|
|
|
|
template <mcc_keyvalue_desc_c DESCR_T,
|
|
const char* COMM_SEQ = MCC_KV_COMMENT_SEQ_ARR, // comment char sequence
|
|
const char* KV_DELIM = MCC_KV_KEY_VALUE_DELIM_SEQ_ARR // key-value delimiter
|
|
>
|
|
class MccKeyValueHolder
|
|
{
|
|
public:
|
|
enum OutputPolicy {
|
|
OPOLICY_CHANGED_ONLY, // serialize only changed and previously deserialized (by fromCharRange method) key-value
|
|
// pairs
|
|
OPOLICY_FULL // serialize entire set of key-value pairs
|
|
};
|
|
|
|
static constexpr std::string_view DEFAULT_RECORD_DELIMITER{"\n"};
|
|
|
|
static constexpr std::string_view COMMENT_SEQ{COMM_SEQ == nullptr ? MCC_KV_COMMENT_SEQ_ARR
|
|
: COMM_SEQ[0] == '\0' ? MCC_KV_COMMENT_SEQ_ARR
|
|
: COMM_SEQ};
|
|
|
|
static constexpr std::string_view KEY_VALUE_DELIM{KV_DELIM == nullptr ? MCC_KV_KEY_VALUE_DELIM_SEQ_ARR
|
|
: KV_DELIM[0] == '\0' ? MCC_KV_KEY_VALUE_DELIM_SEQ_ARR
|
|
: KV_DELIM};
|
|
|
|
static constexpr size_t NUMBER_OF_RECORDS = std::tuple_size_v<DESCR_T>;
|
|
|
|
MccKeyValueHolder(DESCR_T desc)
|
|
: _keyValue(desc), _mapHash{[&desc]<size_t... Is>(std::index_sequence<Is...>) {
|
|
auto arr = std::array{std::make_pair(utils::FNV1aHash(std::get<Is>(desc).key), Is)...};
|
|
std::sort(arr.begin(), arr.end(), [](auto const& p1, auto const& p2) { return p1.first < p2.first; });
|
|
return arr;
|
|
}(std::make_index_sequence<NUMBER_OF_RECORDS>())}
|
|
{
|
|
[this]<size_t... I>(std::index_sequence<I...>) {
|
|
((_hashes[I] = utils::FNV1aHash(std::get<I>(_keyValue).key)), ...);
|
|
}(std::make_index_sequence<NUMBER_OF_RECORDS>());
|
|
|
|
_changedKey.insert_range(_hashes);
|
|
}
|
|
|
|
|
|
constexpr std::array<std::string_view, NUMBER_OF_RECORDS> keys() const
|
|
{
|
|
return [this]<size_t... Is>(std::index_sequence<Is...>) {
|
|
return std::array<std::string_view, NUMBER_OF_RECORDS>{std::get<Is>(_keyValue).key...};
|
|
}(std::make_index_sequence<NUMBER_OF_RECORDS>{});
|
|
}
|
|
|
|
|
|
void setToDefaults()
|
|
{
|
|
[this]<size_t... Is>(std::index_sequence<Is...>) {
|
|
((std::get<Is>(_keyValue).value = std::get<Is>(_keyValue).default_value), ...);
|
|
}(std::make_index_sequence<NUMBER_OF_RECORDS>{});
|
|
}
|
|
|
|
template <typename T, std::ranges::contiguous_range R>
|
|
std::expected<T, std::error_code> getValue(R const& key) const
|
|
{
|
|
return getValue<T>(std::string_view{key});
|
|
}
|
|
|
|
template <typename T>
|
|
std::expected<T, std::error_code> getValue(std::string_view key) const
|
|
{
|
|
// T v;
|
|
// auto err = forKey(key, [&v](auto const& rec) -> std::error_code {
|
|
// using VT = decltype(rec.value);
|
|
// if constexpr (std::convertible_to<VT, T>) {
|
|
// v = rec.value;
|
|
// } else if constexpr (std::constructible_from<T, VT>) {
|
|
// v = T(rec.value);
|
|
// } else {
|
|
// return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE;
|
|
// }
|
|
|
|
// return MccKeyValueHolderErrorCode::ERROR_OK;
|
|
// });
|
|
|
|
// if (err) {
|
|
// return std::unexpected(err);
|
|
// } else {
|
|
// return v;
|
|
// }
|
|
|
|
return forHash1(utils::FNV1aHash(key), [](auto const& rec) {
|
|
using VT = decltype(rec.value);
|
|
if constexpr (std::constructible_from<T, VT>) {
|
|
return T{rec.value};
|
|
} else {
|
|
return std::unexpected(MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE);
|
|
}
|
|
});
|
|
}
|
|
|
|
template <std::ranges::contiguous_range R, typename T>
|
|
std::error_code setValue(R const& key, const T& value)
|
|
{
|
|
return setValue(std::string_view{key}, value);
|
|
}
|
|
|
|
template <typename T>
|
|
std::error_code setValue(std::string_view key, const T& value)
|
|
{
|
|
const size_t hash = utils::FNV1aHash(key);
|
|
|
|
auto ec = forHash(hash, [&value](auto& rec) -> std::error_code {
|
|
using VT = decltype(rec.value);
|
|
if constexpr (std::assignable_from<VT&, T>) {
|
|
rec.value = value;
|
|
} else if constexpr (std::constructible_from<VT, T>) {
|
|
rec.value = VT(value);
|
|
} else {
|
|
return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE;
|
|
}
|
|
|
|
return MccKeyValueHolderErrorCode::ERROR_OK;
|
|
});
|
|
|
|
if (!ec) {
|
|
_changedKey.insert(hash);
|
|
}
|
|
|
|
return ec;
|
|
}
|
|
|
|
template <traits::mcc_input_char_range R,
|
|
traits::mcc_input_char_range RecDelimT = std::string_view,
|
|
mcc_serialization_params_c SerParamsT = mcc_serialization_params_t>
|
|
std::error_code fromCharRange(const R& buffer,
|
|
size_t skip_records = 0, // number of skipped records (from the beginning)
|
|
RecDelimT rec_delim = DEFAULT_RECORD_DELIMITER // records delimiter
|
|
)
|
|
requires std::ranges::contiguous_range<R>
|
|
{
|
|
if constexpr (std::is_array_v<std::decay_t<R>>) { // char*, const char*
|
|
if constexpr (std::is_array_v<std::decay_t<RecDelimT>>) {
|
|
return fromCharRange(std::string_view{buffer}, std::string_view(rec_delim));
|
|
} else {
|
|
return fromCharRange(std::string_view{buffer}, std::move(rec_delim));
|
|
}
|
|
} else {
|
|
if constexpr (std::is_array_v<std::decay_t<RecDelimT>>) {
|
|
return fromCharRange(buffer, std::string_view(rec_delim));
|
|
}
|
|
}
|
|
|
|
std::error_code ec{};
|
|
std::string_view rec, key, svalue, inline_comm;
|
|
std::vector<std::optional<std::string>> head_comm;
|
|
size_t key_hash;
|
|
|
|
_headComment.clear();
|
|
_inlineComment.clear();
|
|
_changedKey.clear();
|
|
|
|
// set values to its defaults
|
|
setToDefaults();
|
|
|
|
auto recs = std::views::split(buffer, std::move(rec_delim)) | std::views::drop(skip_records);
|
|
|
|
for (auto const& el : recs) {
|
|
rec = mcc::utils::trimSpaces(el, utils::TrimType::TRIM_LEFT);
|
|
|
|
inline_comm = {};
|
|
|
|
if (rec.size()) {
|
|
auto found = std::ranges::search(rec, COMMENT_SEQ);
|
|
if (found.begin() != rec.end()) { // there was the comment sequence in record
|
|
if (found.begin() != rec.begin()) { // inline comment
|
|
inline_comm =
|
|
std::string_view{found.begin() + COMMENT_SEQ.size(), rec.end()}; // mark inline comment
|
|
} else { // head comment
|
|
head_comm.push_back(std::string{found.end(), rec.end()});
|
|
}
|
|
|
|
rec = std::string_view(rec.begin(), found.begin());
|
|
}
|
|
|
|
if (rec.size()) {
|
|
found = std::ranges::search(rec, KEY_VALUE_DELIM);
|
|
if (found.begin() != rec.begin()) {
|
|
key = trimSpaces(std::string_view(rec.begin(), found.begin()), utils::TrimType::TRIM_RIGHT);
|
|
svalue = std::string_view(found.end(), rec.end());
|
|
|
|
key_hash = utils::FNV1aHash(key);
|
|
|
|
ec = forHash(key_hash, [&key_hash, svalue, this]<typename REC_T>(REC_T& v) {
|
|
using VT = decltype(v.value);
|
|
auto err = MccDeserializer<VT>{}(svalue, v.value, v.serial_pars);
|
|
if (err) {
|
|
return MccKeyValueHolderErrorCode::ERROR_DESERIAL;
|
|
}
|
|
|
|
_changedKey.insert(key_hash);
|
|
|
|
return MccKeyValueHolderErrorCode::ERROR_OK;
|
|
});
|
|
|
|
if (ec) { // exit in the case of error
|
|
break;
|
|
}
|
|
|
|
// save head comment
|
|
_headComment[key_hash] = head_comm;
|
|
head_comm.clear();
|
|
_inlineComment[key_hash] = inline_comm;
|
|
} else { // an empty key
|
|
ec = MccKeyValueHolderErrorCode::ERROR_INVALID_KEY;
|
|
break;
|
|
}
|
|
} // just comment string starting from the beginning, just skip it (no error)
|
|
|
|
} else { // empty record, just skip it (no error)
|
|
// head_comm.push_back({el.begin(), el.end()});
|
|
head_comm.push_back(std::nullopt);
|
|
}
|
|
}
|
|
|
|
return ec;
|
|
}
|
|
|
|
template <OutputPolicy OPOLICY = MccKeyValueHolder::OPOLICY_CHANGED_ONLY,
|
|
traits::mcc_output_char_range R,
|
|
traits::mcc_input_char_range RecDelimT = std::string_view>
|
|
std::error_code toCharRange(R& output_buffer, RecDelimT rec_delim = DEFAULT_RECORD_DELIMITER)
|
|
{
|
|
if (std::is_pointer_v<std::decay_t<RecDelimT>>) { // char*, const char*, char[], conat char[]
|
|
return toCharRange(output_buffer, std::string_view{rec_delim});
|
|
}
|
|
|
|
std::error_code ec{};
|
|
|
|
auto write_rec = [&output_buffer, &rec_delim, &ec, this]<size_t I = 0>(this auto& self) {
|
|
if constexpr (I < NUMBER_OF_RECORDS) {
|
|
if constexpr (OPOLICY == MccKeyValueHolder::OPOLICY_CHANGED_ONLY) {
|
|
if (_changedKey.count(_hashes[I]) == 0) {
|
|
self.template operator()<I + 1>();
|
|
return;
|
|
}
|
|
}
|
|
|
|
ec = formatRecord<I>(output_buffer, rec_delim);
|
|
if (ec) {
|
|
return;
|
|
}
|
|
|
|
// using val_t = std::remove_cvref_t<decltype(std::get<I>(_keyValue).value)>;
|
|
|
|
// auto key = std::get<I>(_keyValue).key;
|
|
|
|
// std::string buff;
|
|
// auto err =
|
|
// MccSerializer<val_t>{}(buff, std::get<I>(_keyValue).value, std::get<I>(_keyValue).serial_pars);
|
|
// if (err) {
|
|
// ec = MccKeyValueHolderErrorCode::ERROR_SERIAL;
|
|
// return;
|
|
// } else {
|
|
// size_t hash = _hashes[I];
|
|
// // write head comment
|
|
// if (_headComment[hash].size()) {
|
|
// for (auto const& comm : _headComment[hash]) {
|
|
// if (comm.has_value()) {
|
|
// std::format_to(std::back_inserter(output_buffer), "{}{}", COMM_SEQ, comm.value());
|
|
// }
|
|
// std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
|
|
// }
|
|
// }
|
|
|
|
// // key and value
|
|
// std::format_to(std::back_inserter(output_buffer), "{}{}{}", key, KEY_VALUE_DELIM, buff);
|
|
|
|
// // inline comment
|
|
// if (_inlineComment[hash].size()) {
|
|
// std::format_to(std::back_inserter(output_buffer), " {}{}", COMMENT_SEQ,
|
|
// _inlineComment[hash]);
|
|
// }
|
|
|
|
// // record delimiter
|
|
// std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
|
|
// }
|
|
|
|
self.template operator()<I + 1>();
|
|
}
|
|
};
|
|
|
|
write_rec();
|
|
|
|
// [&write_rec]<size_t... Is>(std::index_sequence<Is...>) {
|
|
// (write_rec.template operator()<Is>(), ...);
|
|
// }(std::make_index_sequence<NUMBER_OF_RECORDS>());
|
|
|
|
return ec;
|
|
}
|
|
|
|
protected:
|
|
DESCR_T _keyValue;
|
|
|
|
std::array<size_t, NUMBER_OF_RECORDS> _hashes{}; // key hashes
|
|
std::unordered_map<size_t, std::vector<std::optional<std::string>>>
|
|
_headComment{}; // comment string/strings before key
|
|
std::unordered_map<size_t, std::string> _inlineComment{}; // inline (after key=value pair) comment
|
|
std::unordered_set<size_t> _changedKey{}; // loaded keys (see fromCharRange)
|
|
|
|
const std::array<std::pair<size_t, size_t>, NUMBER_OF_RECORDS> _mapHash;
|
|
|
|
//
|
|
// NOTE: deduced this is needed here to use "forKey" method in getter and setter (const and non-const contexts)!!!
|
|
//
|
|
|
|
std::error_code forKey(this auto&& self, std::string_view key, auto&& func)
|
|
{
|
|
return std::forward<decltype(self)>(self).template forHash<0>(utils::FNV1aHash(key),
|
|
std::forward<decltype(func)>(func));
|
|
}
|
|
|
|
|
|
//
|
|
// 'func' signature:
|
|
// std::error func(mcc_keyvalue_record_c&&)
|
|
// e.g.:
|
|
// [](auto const& rec)->std::error_code {}
|
|
// [](auto& rec)->std::error_code {}
|
|
//
|
|
template <size_t I = 0>
|
|
std::error_code forHash(this auto&& self, size_t hash, auto&& func)
|
|
{
|
|
if constexpr (I < NUMBER_OF_RECORDS) {
|
|
if (hash == std::forward<decltype(self)>(self)._hashes[I]) {
|
|
return std::forward<decltype(func)>(func)(std::get<I>(std::forward<decltype(self)>(self)._keyValue));
|
|
} else {
|
|
return std::forward<decltype(self)>(self).template forHash<I + 1>(hash,
|
|
std::forward<decltype(func)>(func));
|
|
}
|
|
}
|
|
|
|
return MccKeyValueHolderErrorCode::ERROR_INVALID_KEY;
|
|
}
|
|
|
|
template <typename FuncT>
|
|
auto forHash1(this auto&& self, size_t hash, FuncT&& func)
|
|
{
|
|
return std::forward<decltype(self)>(self).template forHash1Impl<NUMBER_OF_RECORDS / 2>(
|
|
hash, std::forward<FuncT>(func), 0, NUMBER_OF_RECORDS - 1);
|
|
}
|
|
|
|
//
|
|
// implements a binary search
|
|
//
|
|
template <size_t I = NUMBER_OF_RECORDS / 2, typename FuncT>
|
|
auto forHash1Impl(this auto&& self,
|
|
size_t hash,
|
|
FuncT&& func,
|
|
size_t start = 0,
|
|
size_t stop = NUMBER_OF_RECORDS - 1)
|
|
{
|
|
using func_t = std::remove_cvref_t<FuncT>;
|
|
|
|
if constexpr (I < NUMBER_OF_RECORDS) {
|
|
if (hash == std::forward<decltype(self)>(self)._mapHash[I].first) {
|
|
constexpr auto idx = std::forward<decltype(self)>(self)._mapHash[I].second;
|
|
using rec_t = std::tuple_element_t<idx, DESCR_T>;
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<func_t, rec_t>>) {
|
|
std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
|
|
} else {
|
|
return std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
|
|
}
|
|
} else if (hash < std::forward<decltype(self)>(self)._mapHash[I].first) {
|
|
if (stop - start > 1) {
|
|
return std::forward<decltype(self)>(self).template forHash1Impl<I / 2>(
|
|
hash, std::forward<FuncT>(func), start, I - 1);
|
|
}
|
|
|
|
const auto idx = self._mapHash[I - 1].second;
|
|
using rec_t = std::tuple_element_t<idx, DESCR_T>;
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<func_t, rec_t>>) {
|
|
std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
|
|
} else {
|
|
return std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
|
|
}
|
|
} else { // hash > std::forward<decltype(self)>(self)._mapHash[I].first
|
|
if (stop - start > 1) {
|
|
return std::forward<decltype(self)>(self).template forHash1Impl<I / 2>(
|
|
hash, std::forward<FuncT>(func), I + 1, stop);
|
|
}
|
|
|
|
const auto idx = self._mapHash[I + 1].second;
|
|
using rec_t = std::tuple_element_t<idx, DESCR_T>;
|
|
|
|
if constexpr (std::is_void_v<std::invoke_result_t<func_t, rec_t>>) {
|
|
std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
|
|
} else {
|
|
return std::forward<FuncT>(func)(std::get<idx>(std::forward<decltype(self)>(self)._keyValue));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <size_t I = 0, traits::mcc_output_char_range R, traits::mcc_input_char_range RecDelimT>
|
|
std::error_code formatRecord(R& output_buffer, RecDelimT rec_delim, bool is_default = false)
|
|
{
|
|
if constexpr (I < NUMBER_OF_RECORDS) {
|
|
auto key = std::get<I>(_keyValue).key;
|
|
using val_t = std::remove_cvref_t<decltype(std::get<I>(_keyValue).value)>;
|
|
|
|
const val_t& val = is_default ? std::get<I>(_keyValue).default_value : std::get<I>(_keyValue).value;
|
|
|
|
std::string buff;
|
|
// auto err = MccSerializer<val_t>{}(buff, std::get<I>(_keyValue).value,
|
|
// std::get<I>(_keyValue).serial_pars);
|
|
auto err = MccSerializer<val_t>{}(buff, val, std::get<I>(_keyValue).serial_pars);
|
|
if (err) {
|
|
return MccKeyValueHolderErrorCode::ERROR_SERIAL;
|
|
} else {
|
|
size_t hash = _hashes[I];
|
|
// write head comment
|
|
if (_headComment[hash].size()) {
|
|
for (auto const& comm : _headComment[hash]) {
|
|
if (comm.has_value()) {
|
|
std::format_to(std::back_inserter(output_buffer), "{}{}", COMM_SEQ, comm.value());
|
|
}
|
|
std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
|
|
}
|
|
}
|
|
|
|
// key and value
|
|
std::format_to(std::back_inserter(output_buffer), "{}{}{}", key, KEY_VALUE_DELIM, buff);
|
|
|
|
// inline comment
|
|
if (_inlineComment[hash].size()) {
|
|
std::format_to(std::back_inserter(output_buffer), " {}{}", COMMENT_SEQ, _inlineComment[hash]);
|
|
}
|
|
|
|
// record delimiter
|
|
std::ranges::copy(rec_delim, std::back_inserter(output_buffer));
|
|
}
|
|
}
|
|
|
|
return MccKeyValueHolderErrorCode::ERROR_OK;
|
|
}
|
|
};
|
|
|
|
template <mcc_record_value_c T>
|
|
struct mcc_simple_kv_record_t {
|
|
const std::string_view key;
|
|
T value, default_value;
|
|
mcc_serialization_params_t serial_pars;
|
|
};
|
|
|
|
template <mcc_record_value_c T>
|
|
static mcc_simple_kv_record_t<T> mcc_make_simple_kv_record(
|
|
std::string_view key,
|
|
T const& def_value,
|
|
mcc_serialization_params_t const& spars = mcc_serialization_params_t{})
|
|
{
|
|
return mcc_simple_kv_record_t<T>{key, def_value, def_value, spars};
|
|
}
|
|
|
|
} // namespace mcc::impl
|