616 lines
23 KiB
C++
616 lines
23 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); };
|
|
|
|
|
|
// use of 'inline' here to avoid '-Wsubobject-linkage' GCC-warning
|
|
inline constexpr char MCC_KV_COMMENT_SEQ_ARR[] = "#";
|
|
inline constexpr char MCC_KV_KEY_VALUE_DELIM_SEQ_ARR[] = "=";
|
|
inline 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)
|
|
{
|
|
[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);
|
|
}
|
|
|
|
MccKeyValueHolder(MccKeyValueHolder const& other) : MccKeyValueHolder(other._keyValue)
|
|
{
|
|
copyInst(other);
|
|
};
|
|
|
|
MccKeyValueHolder(MccKeyValueHolder&& other)
|
|
{
|
|
moveInst(std::move(other));
|
|
};
|
|
|
|
MccKeyValueHolder& operator=(MccKeyValueHolder const& other)
|
|
{
|
|
copyInst(other);
|
|
|
|
return *this;
|
|
};
|
|
|
|
MccKeyValueHolder& operator=(MccKeyValueHolder&& other)
|
|
{
|
|
moveInst(std::move(other));
|
|
|
|
return *this;
|
|
};
|
|
|
|
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
|
|
{
|
|
// ugly, but works for non-default constructible values
|
|
alignas(T) unsigned char buff[sizeof(T)];
|
|
auto err = forKey(key, [&buff](auto const& rec) -> std::error_code {
|
|
using VT = decltype(rec.value);
|
|
if constexpr (std::constructible_from<T, VT>) {
|
|
new (buff) T(rec.value);
|
|
} else {
|
|
return MccKeyValueHolderErrorCode::ERROR_INCOMPATIBLE_TYPE;
|
|
}
|
|
|
|
return MccKeyValueHolderErrorCode::ERROR_OK;
|
|
});
|
|
|
|
if (err) {
|
|
return std::unexpected(err);
|
|
} else {
|
|
auto ptr = reinterpret_cast<T*>(&buff[0]);
|
|
auto v = std::move(*ptr);
|
|
ptr->~T(); // one needs explicitly call destructor here to avvoid side effects!!!
|
|
|
|
return v;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
// }
|
|
// }
|
|
|
|
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 (requires { rec.value = value; }) {
|
|
rec.value = value;
|
|
} else if constexpr (std::constructible_from<VT, T>) {
|
|
rec.value = VT(value);
|
|
} else if constexpr (std::ranges::range<VT> && std::ranges::range<T> &&
|
|
requires(std::ranges::range_value_t<VT> v, std::ranges::range_value_t<T> t) {
|
|
v = t;
|
|
}) {
|
|
const auto& val = std::is_pointer_v<std::decay_t<T>> ? std::span{value} : value;
|
|
|
|
size_t N = std::ranges::distance(rec.value.begin(), rec.value.end());
|
|
size_t M = std::ranges::distance(val.begin(), val.end());
|
|
|
|
auto it = std::ranges::begin(rec.value);
|
|
std::ranges::for_each_n(val.begin(), N > M ? M : N, [&it](auto const& el) {
|
|
*it = el;
|
|
++it;
|
|
});
|
|
|
|
if (N >= M) { // resize to match the size of the input range
|
|
if constexpr (!traits::mcc_non_resizable_range<VT>) {
|
|
rec.value.resize(M);
|
|
}
|
|
} else { // append if allowed
|
|
if constexpr (!traits::mcc_fixed_size_range<VT>) {
|
|
std::ranges::for_each_n(val.begin(), N, [&it](auto const& el) {
|
|
*it = el;
|
|
++it;
|
|
});
|
|
|
|
std::ranges::for_each(val | std::views::drop(N),
|
|
[&rec](auto const& el) { std::back_inserter(rec.value) = el; });
|
|
}
|
|
}
|
|
} 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{};
|
|
|
|
#ifdef __GNUG__
|
|
// to fix GCC compilation crash for the versions < 16
|
|
#if GCC_VERSION < 160000
|
|
auto write_rec = [&output_buffer, &rec_delim, &ec, obj_ptr = this]<size_t I = 0>(this auto& self) -> void {
|
|
#else
|
|
auto write_rec = [&output_buffer, &rec_delim, &ec, this]<size_t I = 0>(this auto& self) -> void {
|
|
#endif
|
|
#endif
|
|
if constexpr (I < NUMBER_OF_RECORDS) {
|
|
if constexpr (OPOLICY == MccKeyValueHolder::OPOLICY_CHANGED_ONLY) {
|
|
#ifdef __GNUG__
|
|
// to fix GCC compilation crash for the versions < 16
|
|
#if GCC_VERSION < 160000
|
|
if (obj_ptr->_changedKey.count(obj_ptr->_hashes[I]) == 0) {
|
|
#else
|
|
if (_changedKey.count(_hashes[I]) == 0) {
|
|
#endif
|
|
#endif
|
|
self.template operator()<I + 1>();
|
|
return;
|
|
}
|
|
}
|
|
|
|
ec = formatRecord<I>(output_buffer, rec_delim);
|
|
#ifdef __GNUG__
|
|
// to fix GCC compilation crash for the versions < 16
|
|
#if GCC_VERSION < 160000
|
|
ec = obj_ptr->template formatRecord<I>(output_buffer, rec_delim);
|
|
#else
|
|
ec = formatRecord<I>(output_buffer, rec_delim);
|
|
#endif
|
|
#endif
|
|
if (ec) {
|
|
return;
|
|
}
|
|
|
|
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)
|
|
|
|
//
|
|
// 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 <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 <size_t I = 0>
|
|
void copyInst(MccKeyValueHolder const& other)
|
|
{
|
|
if constexpr (I < NUMBER_OF_RECORDS) {
|
|
// here one needs check equality of the keys!!!
|
|
if (_hashes[I] == other._hashes[I]) {
|
|
auto& orec = std::get<I>(other._keyValue);
|
|
std::get<I>(_keyValue).value = orec.value;
|
|
std::get<I>(_keyValue).default_value = orec.default_value;
|
|
std::get<I>(_keyValue).serial_pars = orec.serial_pars;
|
|
|
|
if (auto it = other._headComment.find(_hashes[I]); it != other._headComment.end()) {
|
|
_headComment[_hashes[I]] = it->second;
|
|
}
|
|
if (auto it = other._inlineComment.find(_hashes[I]); it != other._inlineComment.end()) {
|
|
_inlineComment[_hashes[I]] = it->second;
|
|
}
|
|
|
|
_changedKey.insert(_hashes[I]);
|
|
}
|
|
|
|
copyInst<I + 1>(other);
|
|
}
|
|
}
|
|
|
|
template <size_t I = 0>
|
|
void moveInst(MccKeyValueHolder&& other)
|
|
{
|
|
if constexpr (I < NUMBER_OF_RECORDS) {
|
|
// here one needs check equality of the keys!!!
|
|
if (_hashes[I] == other._hashes[I]) {
|
|
auto& orec = std::get<I>(other._keyValue);
|
|
std::get<I>(_keyValue).value = std::move(orec.value);
|
|
std::get<I>(_keyValue).default_value = std::move(orec.default_value);
|
|
std::get<I>(_keyValue).serial_pars = std::move(orec.serial_pars);
|
|
|
|
if (auto it = other._headComment.find(_hashes[I]); it != other._headComment.end()) {
|
|
_headComment[_hashes[I]] = std::move(it->second);
|
|
}
|
|
if (auto it = other._inlineComment.find(_hashes[I]); it != other._inlineComment.end()) {
|
|
_inlineComment[_hashes[I]] = std::move(it->second);
|
|
}
|
|
|
|
_changedKey.insert(_hashes[I]);
|
|
}
|
|
|
|
moveInst<I + 1>(other);
|
|
}
|
|
}
|
|
};
|
|
|
|
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
|