include/core/CPersistUtils.h (883 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the following additional limitation. Functionality enabled by the
* files subject to the Elastic License 2.0 may only be used in production when
* invoked by an Elasticsearch process with a license key installed that permits
* use of machine learning features. You may not use this file except in
* compliance with the Elastic License 2.0 and the foregoing additional
* limitation.
*/
#ifndef INCLUDED_ml_core_CPersistUtils_h
#define INCLUDED_ml_core_CPersistUtils_h
#include <core/CFloatStorage.h>
#include <core/CLogger.h>
#include <core/CStatePersistInserter.h>
#include <core/CStateRestoreTraverser.h>
#include <core/CStringUtils.h>
#include <core/ImportExport.h>
#include <boost/iterator/indirect_iterator.hpp>
#include <boost/unordered/unordered_map_fwd.hpp>
#include <boost/unordered/unordered_set_fwd.hpp>
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>
namespace ml {
namespace core {
namespace persist_utils_detail {
const TPersistenceTag FIRST_TAG("a", "first");
const TPersistenceTag SECOND_TAG("b", "second");
const TPersistenceTag MAP_TAG("c", "map");
const TPersistenceTag SIZE_TAG("d", "size");
const std::string TUPLE_PREFIX{"t_"};
template<typename T>
struct remove_const {
using type = typename std::remove_const<T>::type;
};
template<typename U, typename V>
struct remove_const<std::pair<U, V>> {
using type = std::pair<typename remove_const<U>::type, typename remove_const<V>::type>;
};
//! Template specialisation utility classes for selecting various approaches
//! for persisting and restoring objects.
class BasicPersist {};
class ContainerPersist {};
class MemberPersist {};
class MemberToDelimited {};
class BasicRestore {};
class ContainerRestore {};
class MemberRestore {};
class MemberFromDelimited {};
//! \name Class used to select the appropriate persist implementation for containers.
//@{
template<typename T, typename ITR = void>
struct persist_container_selector {
using value = BasicPersist;
};
template<typename T>
struct persist_container_selector<T, std::void_t<typename T::const_iterator>> {
using value = ContainerPersist;
};
//@}
//! \name Class used to select the appropriate persist implementation.
//@{
// clang-format off
template<typename T, typename ENABLE = void>
struct persist_selector {
using value = typename persist_container_selector<T>::value;
};
template<typename T>
struct persist_selector<T, std::enable_if_t<
std::is_same_v<decltype(&T::acceptPersistInserter), void (T::*)(CStatePersistInserter&) const>>> {
using value = MemberPersist;
};
template<typename T>
struct persist_selector<T, std::enable_if_t<
std::is_same_v<decltype(&T::toDelimited), std::string (T::*)() const>>> {
using value = MemberToDelimited;
};
// clang-format on
//@}
//! \brief Specialisations of this class implement persist methods based on object features.
template<typename SELECTOR>
class CPersisterImpl {};
//! Convenience function to call the appropriate persist implementation.
template<typename T>
bool persist(const std::string& tag, const T& target, CStatePersistInserter& inserter) {
CPersisterImpl<typename persist_selector<T>::value>::dispatch(tag, target, inserter);
return true;
}
//! \name Class used to select the appropriate restore implementation for containers.
//@{
template<typename T, typename ITR = void>
struct restore_container_selector {
using value = BasicRestore;
};
template<typename T>
struct restore_container_selector<T, std::void_t<typename T::iterator>> {
using value = ContainerRestore;
};
//@}
//! \name Class used to select appropriate restore implementation.
//@{
// clang-format off
template<typename T, typename ENABLE = void>
struct restore_selector {
using value = typename restore_container_selector<T>::value;
};
template<typename T>
struct restore_selector<T, std::enable_if_t<
std::is_same_v<decltype(&T::acceptRestoreTraverser), bool (T::*)(CStateRestoreTraverser&)>>> {
using value = MemberRestore;
};
template<typename T>
struct restore_selector<T, std::enable_if_t<
std::is_same_v<decltype(&T::fromDelimited), bool (T::*)(const std::string&)>>> {
using value = MemberFromDelimited;
};
// clang-format on
//@}
//! \brief Specialisations of this class implement restore methods based on object features.
template<typename SELECTOR>
class CRestorerImpl {};
//! Convenience function to call the appropriate restore implementation.
template<typename T>
bool restore(const std::string& tag, T& target, CStateRestoreTraverser& traverser) {
return CRestorerImpl<typename restore_selector<T>::value>::dispatch(tag, target, traverser);
}
//! Tag for classes which have a void reserve(std::size_t) method.
class CanReserve {};
//! \name Class used to check for the reserve function.
//@{
// clang-format off
template<typename T, typename ENABLE = void>
struct reserve_selector {
using value = ENABLE;
};
template<typename T>
struct reserve_selector<T, std::enable_if_t<
std::is_same_v<decltype(&T::reserve), void (T::*)(std::size_t)>>> {
using value = CanReserve;
};
// clang-format on
//@}
//! \brief Implementation of the pre-allocation class for objects which don't
//! support pre-allocation - i.e. do nothing.
template<typename SELECTOR>
class CReserveImpl {
public:
template<typename T>
static void dispatch(const T&, std::size_t) {}
};
//! \brief Implementation of the pre-allocation class for objects which have
//! a void reserve(std::size_t) method.
template<>
class CReserveImpl<CanReserve> {
public:
template<typename T>
static void dispatch(T& t, std::size_t size) {
t.reserve(size);
}
};
//! Convenience function to call the appropriate reserve implementation.
template<typename T>
void reserve(T& t, std::size_t size) {
CReserveImpl<typename reserve_selector<T>::value>::dispatch(t, size);
}
} // persist_utils_detail::
//! \brief A class of persistence patterns.
//!
//! DESCRIPTION:\n
//! Various patterns come up repeatedly in persistence. For example, it is much
//! more efficient to persist containers of basic types to a single string rather
//! than as separate elements in the state document. This also means that we can
//! reserve vectors on restore.
class CORE_EXPORT CPersistUtils {
public:
static const char DELIMITER;
static const char PAIR_DELIMITER;
public:
//! \brief Converts a built in type to a string using CStringUtils functions.
class CORE_EXPORT CBuiltinToString {
public:
explicit CBuiltinToString(const char pairDelimiter)
: m_PairDelimiter(pairDelimiter) {}
std::string operator()(double value) const {
return CStringUtils::typeToStringPrecise(value, CIEEE754::E_DoublePrecision);
}
template<typename T>
std::string operator()(T value) const {
return CStringUtils::typeToString(value);
}
std::string operator()(std::int8_t value) const {
return CStringUtils::typeToString(static_cast<int>(value));
}
std::string operator()(std::uint8_t value) const {
return CStringUtils::typeToString(static_cast<unsigned int>(value));
}
std::string operator()(std::int16_t value) const {
return CStringUtils::typeToString(static_cast<int>(value));
}
std::string operator()(std::uint16_t value) const {
return CStringUtils::typeToString(static_cast<unsigned int>(value));
}
std::string operator()(CFloatStorage value) const {
return value.toString();
}
template<typename T>
std::string operator()(const std::optional<T>& value) const {
return value ? this->operator()(std::make_pair(true, *value))
: this->operator()(std::make_pair(false, T{}));
}
template<typename U, typename V>
std::string operator()(const std::pair<U, V>& value) const {
return this->operator()(value.first) + m_PairDelimiter +
this->operator()(value.second);
}
template<typename... T>
std::string operator()(const std::tuple<T...>& value) const {
std::ostringstream result;
std::apply(
[&](const auto&... element) {
((result << this->operator()(element) << m_PairDelimiter), ...);
},
value);
return result.str();
}
template<typename T>
std::string operator()(const std::atomic<T>& value) const {
return this->operator()(value.load());
}
private:
char m_PairDelimiter;
};
//! \brief Converts a string to a built in type using CStringUtils functions.
class CORE_EXPORT CBuiltinFromString {
public:
explicit CBuiltinFromString(const char pairDelimiter)
: m_PairDelimiter(pairDelimiter) {}
template<typename T>
bool operator()(const std::string& token, T& value) const {
return CStringUtils::stringToType(token, value);
}
bool operator()(const std::string& token, std::int8_t& value) const {
int value_;
if (CStringUtils::stringToType(token, value_)) {
value = static_cast<std::int8_t>(value_);
return true;
}
return false;
}
bool operator()(const std::string& token, std::uint8_t& value) const {
unsigned int value_;
if (CStringUtils::stringToType(token, value_)) {
value = static_cast<std::uint8_t>(value_);
return true;
}
return false;
}
bool operator()(const std::string& token, std::int16_t& value) const {
int value_;
if (CStringUtils::stringToType(token, value_)) {
value = static_cast<std::int16_t>(value_);
return true;
}
return false;
}
bool operator()(const std::string& token, std::uint16_t& value) const {
unsigned int value_;
if (CStringUtils::stringToType(token, value_)) {
value = static_cast<std::uint16_t>(value_);
return true;
}
return false;
}
bool operator()(const std::string& token, CFloatStorage& value) const {
return value.fromString(token);
}
template<typename T>
bool operator()(const std::string& token, std::optional<T>& value) const {
std::pair<bool, T> pairValue;
if (this->operator()(token, pairValue)) {
value = pairValue.first ? std::optional<T>{pairValue.second}
: std::optional<T>{};
return true;
}
return false;
}
template<typename U, typename V>
bool operator()(const std::string& token, std::pair<U, V>& value) const {
std::size_t delimPos(token.find(m_PairDelimiter));
if (delimPos == std::string::npos) {
return false;
}
m_Token.assign(token, 0, delimPos);
if (this->operator()(m_Token, value.first) == false) {
return false;
}
m_Token.assign(token, delimPos + 1, token.length() - delimPos);
return this->operator()(m_Token, value.second);
}
template<typename... T>
bool operator()(const std::string& token, std::tuple<T...>& value) const {
if (std::count(token.begin(), token.end(), m_PairDelimiter) != sizeof...(T)) {
return false;
}
auto popOneItem = [this](std::string_view& tokenView, auto& element) {
std::size_t delimPos(tokenView.find(m_PairDelimiter));
if (delimPos == std::string::npos) {
return false;
}
m_Token = tokenView.substr(0, delimPos);
bool result{this->operator()(m_Token, element)};
tokenView.remove_prefix(delimPos + 1);
return result;
};
bool success{true};
std::string_view tokenView{token};
std::apply(
[&](auto&... element) {
// Note we want to shortcircuit if not successful.
((success = success && popOneItem(tokenView, element)), ...);
},
value);
return success;
}
template<typename T>
bool operator()(const std::string& token, std::atomic<T>& value) const {
T value_;
bool result{CStringUtils::stringToType(token, value_)};
value.store(value_);
return result;
}
private:
char m_PairDelimiter;
mutable std::string m_Token;
};
//! Restore \p collection reading state from \p traverser expecting \p tag for elements.
template<typename T>
static bool restore(const std::string& tag, T& collection, CStateRestoreTraverser& traverser) {
return persist_utils_detail::restore(tag, collection, traverser);
}
//! Persist \p collection passing state to \p inserter using \p tag for elements.
template<typename T>
static void
persist(const std::string& tag, const T& collection, CStatePersistInserter& inserter) {
persist_utils_detail::persist(tag, collection, inserter);
}
//! Persist \p collection passing state to \p inserter using \p tag for elements.
template<typename T>
static void
persist(const TPersistenceTag& tag, const T& collection, CStatePersistInserter& inserter) {
persist_utils_detail::persist(tag.name(inserter.readableTags()), collection, inserter);
}
//! A convenience function for optionally persisting types which convert to null.
//!
//! \tparam DEREFERENCEABLE Must support conversion to bool and operator*.
template<typename DEREFERENCEABLE>
static void persistIfNotNull(const std::string& tag,
const DEREFERENCEABLE& target,
CStatePersistInserter& inserter) {
if (target) {
persist_utils_detail::persist(tag, *target, inserter);
}
}
//! A convenience function for optionally persisting types which convert to null.
//!
//! \tparam DEREFERENCEABLE Must support conversion to bool and operator*.
template<typename DEREFERENCEABLE>
static void persistIfNotNull(const TPersistenceTag& tag,
const DEREFERENCEABLE& target,
CStatePersistInserter& inserter) {
if (target) {
persist_utils_detail::persist(tag.name(inserter.readableTags()), *target, inserter);
}
}
//! Wrapper for containers of built in types.
template<typename CONTAINER>
static std::string toString(const CONTAINER& collection,
const char delimiter = DELIMITER,
const char pairDelimiter = PAIR_DELIMITER) {
CBuiltinToString f(pairDelimiter);
return toString(collection, f, delimiter);
}
//! Convert a collection to a string.
//!
//! \param[in] collection The collection to persist.
//! \param[in] stringFunc The function used to persist elements.
//! \param[in] delimiter The delimiter used to separate elements.
//! \note This should use RVO so just return the string.
template<typename CONTAINER, typename F>
static std::string toString(const CONTAINER& collection,
const F& stringFunc,
const char delimiter = DELIMITER) {
if (collection.empty()) {
return std::string();
}
auto begin = collection.begin();
auto end = collection.end();
return toString(begin, end, stringFunc, delimiter);
}
//! Wrapper for containers of built in types.
template<typename ITR>
static std::string toString(ITR& begin,
ITR& end,
const char delimiter = DELIMITER,
const char pairDelimiter = PAIR_DELIMITER) {
CBuiltinToString f(pairDelimiter);
return toString(begin, end, f, delimiter);
}
//! Convert the range between two iterators to a string.
//!
//! \param[in,out] begin The iterator at the start of the range. This will
//! be equal to end when the function returns.
//! \param[in] end The iterator at the end of the range.
//! \param[in] stringFunc The function used to persist elements.
//! \param[in] delimiter The delimiter used to separate elements.
//! \note This should use RVO so just return the string.
template<typename ITR, typename F>
static std::string
toString(ITR& begin, ITR& end, const F& stringFunc, const char delimiter = DELIMITER) {
std::string result{stringFunc(*begin++)};
for (/**/; begin != end; ++begin) {
result += delimiter;
result += stringFunc(*begin);
}
return result;
}
//! Wrapper for arrays of built in types.
template<typename T, std::size_t N>
static bool fromString(const std::string& state,
std::array<T, N>& collection,
const char delimiter = DELIMITER,
const char pairDelimiter = PAIR_DELIMITER) {
CBuiltinFromString f(pairDelimiter);
return fromString(state, f, collection, delimiter);
}
//! Wrapper for containers of built in types.
template<typename CONTAINER>
static bool fromString(const std::string& state,
CONTAINER& collection,
const char delimiter = DELIMITER,
const char pairDelimiter = PAIR_DELIMITER,
bool append = false) {
CBuiltinFromString f(pairDelimiter);
return fromString(state, f, collection, delimiter, append);
}
//! Wrapper for ranges of built in types.
template<typename ITR>
static bool fromString(const std::string& state,
ITR begin,
ITR end,
const char delimiter = DELIMITER,
const char pairDelimiter = PAIR_DELIMITER) {
CBuiltinFromString f(pairDelimiter);
return fromString(state, f, begin, end, delimiter);
}
//! Restore a vector from a string created by toString.
//!
//! \param[in] state The string description of the collection.
//! \param[in] stringFunc The function used to restore elements.
//! \param[out] collection Filled in with elements extracted from \p state.
//! \param[in] delimiter The delimiter used to separate elements.
//! \param[in] append If true append the results to the collection otherwise
//! it is cleared first.
//! \return True if there was no error parsing \p state and false otherwise.
//! \note If the state cannot be parsed then \p collection is cleared.
//! \note T must have a default constructor.
//! \note The delimiter must match the delimiter used for persistence.
//! \tparam F Expected to have the signature:
//! \code
//! bool (const std::string &, T &)
//! \endcode
template<typename T, typename F>
static bool fromString(const std::string& state,
const F& stringFunc,
std::vector<T>& collection,
const char delimiter = DELIMITER,
const bool append = false) {
if (append == false) {
collection.clear();
}
if (state.empty()) {
return true;
}
collection.reserve(std::count(state.begin(), state.end(), delimiter) + 1);
if (fromString<T>(state, delimiter, stringFunc,
std::back_inserter(collection)) == false) {
collection.clear();
return false;
}
return true;
}
//! Restore a std::array from a string created by toString.
//!
//! \param[in] state The string description of the collection.
//! \param[in] stringFunc The function used to restore elements.
//! \param[out] collection Filled in with elements extracted from \p state.
//! \param[in] delimiter The delimiter used to separate elements.
//! \return True if there was no error parsing \p state and it contained
//! exactly N elements and false otherwise.
//! \note The delimiter must match the delimiter used for persistence.
//! \tparam F Expected to have the signature:
//! \code
//! bool (const std::string &, T &)
//! \endcode
template<typename T, std::size_t N, typename F>
static bool fromString(const std::string& state,
const F& stringFunc,
std::array<T, N>& collection,
const char delimiter = DELIMITER) {
if (state.empty()) {
LOG_ERROR(<< "Unexpected number of elements 0, expected " << N);
return false;
}
std::size_t n{static_cast<std::size_t>(
std::count(state.begin(), state.end(), delimiter) + 1)};
if (n != N) {
LOG_ERROR(<< "Unexpected number of elements " << n << ", expected " << N);
return false;
}
return fromString<T>(state, delimiter, stringFunc, collection.begin());
}
//! Restore a container from a string created by toString.
//!
//! \param[in] state The string description of the collection.
//! \param[in] stringFunc The function used to restore elements.
//! \param[out] collection Filled in with elements extracted from \p state.
//! \param[in] delimiter The delimiter used to separate elements.
//! \param[in] append If true append the results to the collection otherwise
//! it is cleared first
//! \return True if there was no error parsing \p state and false otherwise.
//! \note If the state cannot be parsed then \p collection is cleared.
//! \note The container value type must have a default constructor.
//! \note The delimiter must match the delimiter used for persistence.
//! \tparam F Expected to have the signature:
//! \code{.cpp}
//! bool (const std::string &, CONTAINER::value_type &)
//! \endcode
template<typename CONTAINER, typename F>
static bool fromString(const std::string& state,
const F& stringFunc,
CONTAINER& collection,
const char delimiter = DELIMITER,
bool append = false) {
using T = typename persist_utils_detail::remove_const<typename CONTAINER::value_type>::type;
if (append == false) {
collection.clear();
}
if (state.empty()) {
return true;
}
if (fromString<T>(state, delimiter, stringFunc,
std::inserter(collection, collection.end())) == false) {
collection.clear();
return false;
}
return true;
}
//! Restore a range from a string created by toString.
//!
//! \param[in] state The string description of the range.
//! \param[in] stringFunc The function used to restore elements.
//! \param[out] begin Filled in with elements extracted from \p state.
//! \param[in] end The end of the range into which to restore the elements.
//! \param[in] delimiter The delimiter used to separate elements.
//! \return True if there was no error parsing \p state and false otherwise.
//! \note The container value type must have a default constructor.
//! \note The delimiter must match the delimiter used for persistence.
//! \tparam F Expected to have the signature:
//! \code{.cpp}
//! bool (const std::string &, CONTAINER::value_type &)
//! \endcode
template<typename ITR, typename F>
static bool fromString(const std::string& state,
const F& stringFunc,
ITR begin,
ITR end,
const char delimiter = DELIMITER) {
if (state.empty()) {
return true;
}
auto n = std::count(state.begin(), state.end(), delimiter) + 1;
auto N = std::distance(begin, end);
if (n != N) {
LOG_ERROR(<< "Unexpected number of elements " << n << ", expected " << N);
return false;
}
return fromString<typename std::iterator_traits<ITR>::value_type>(
state, delimiter, stringFunc, begin);
}
private:
//! Restores to an insertion iterator.
template<typename T, typename F, typename ITR>
static bool
fromString(const std::string& state, const char delimiter, const F& stringFunc, ITR inserter) {
std::size_t delimPos{state.find(delimiter)};
if (delimPos == std::string::npos) {
T element;
if (stringFunc(state, element) == false) {
LOG_ERROR(<< "Invalid state " << state);
return false;
}
*inserter = std::move(element);
++inserter;
return true;
}
// Reuse this same string to avoid as many allocations as possible.
std::string token;
token.assign(state, 0, delimPos);
{
T element;
if (stringFunc(token, element) == false) {
LOG_ERROR(<< "Invalid element 0 : element " << token << " in " << state);
return false;
}
*inserter = std::move(element);
++inserter;
}
std::size_t i{1};
std::size_t lastDelimPos{delimPos};
while (lastDelimPos != std::string::npos) {
delimPos = state.find(delimiter, lastDelimPos + 1);
if (delimPos == std::string::npos) {
token.assign(state, lastDelimPos + 1, state.length() - lastDelimPos);
} else {
token.assign(state, lastDelimPos + 1, delimPos - lastDelimPos - 1);
}
T element;
if (stringFunc(token, element) == false) {
LOG_ERROR(<< "Invalid element " << i << " : element " << token
<< " in " << state);
return false;
}
*inserter = std::move(element);
++i;
lastDelimPos = delimPos;
++inserter;
}
return true;
}
};
namespace persist_utils_detail {
//! Basic persist functionality implementation, for PODs or pairs
template<>
class CPersisterImpl<BasicPersist> {
public:
template<typename T>
static void dispatch(const std::string& tag, const T& t, CStatePersistInserter& inserter) {
CPersistUtils::CBuiltinToString toString(CPersistUtils::PAIR_DELIMITER);
inserter.insertValue(tag, toString(t));
}
template<typename A, typename B>
static void dispatch(const std::string& tag,
const std::pair<A, B>& t,
CStatePersistInserter& inserter) {
inserter.insertLevel(tag,
[&t](auto& inserter_) { newLevel(t, inserter_); });
}
template<typename... T>
static void dispatch(const std::string& tag,
const std::tuple<T...>& t,
CStatePersistInserter& inserter) {
auto newLevel = [](std::size_t pos, const auto& element,
CStatePersistInserter& inserter_) {
persist(TUPLE_PREFIX + std::to_string(pos), element, inserter_);
};
inserter.insertLevel(tag, [&](auto& inserter_) {
std::size_t pos{0};
std::apply(
[&](const auto&... element) {
((newLevel(pos++, element, inserter_)), ...);
},
t);
});
}
private:
template<typename A, typename B>
static void newLevel(const std::pair<A, B>& t, CStatePersistInserter& inserter) {
persist(FIRST_TAG, t.first, inserter);
persist(SECOND_TAG, t.second, inserter);
}
};
//! \brief Persister class for containers. If contained types are PODs they
//! are written as a delimited string, or strings written as straight strings,
//! else added as a new level and re-dispatched
template<>
class CPersisterImpl<ContainerPersist> {
public:
template<typename T>
static void
dispatch(const std::string& tag, const T& container, CStatePersistInserter& inserter) {
doInsert(tag, container, inserter,
std::integral_constant<bool, std::is_arithmetic<typename T::value_type>::value>{},
std::false_type{});
}
//! Specialisation for boost::unordered_set which orders values.
template<typename T, typename H, typename P, typename A>
static void dispatch(const std::string& tag,
const boost::unordered_set<T, H, P, A>& container,
CStatePersistInserter& inserter) {
using TVec = typename std::vector<T>;
using TCItr = typename boost::unordered_set<T, H, P, A>::const_iterator;
using TCItrVec = typename std::vector<TCItr>;
if (std::is_arithmetic<T>::value) {
TVec values(container.begin(), container.end());
std::sort(values.begin(), values.end());
doInsert(tag, values, inserter, std::true_type{}, std::false_type{});
} else {
TCItrVec iterators;
iterators.reserve(container.size());
for (auto i = container.begin(); i != container.end(); ++i) {
iterators.push_back(i);
}
// Sort the values to ensure consistent persist state.
std::sort(iterators.begin(), iterators.end(),
[](TCItr lhs, TCItr rhs) { return *lhs < *rhs; });
doInsert(tag, iterators, inserter, std::false_type{}, std::true_type{});
}
}
//! Specialisation for boost::unordered_map which orders values.
template<typename K, typename V, typename H, typename P, typename A>
static void dispatch(const std::string& tag,
const boost::unordered_map<K, V, H, P, A>& container,
CStatePersistInserter& inserter) {
using TCItr = typename boost::unordered_map<K, V, H, P, A>::const_iterator;
using TCItrVec = typename std::vector<TCItr>;
TCItrVec iterators;
iterators.reserve(container.size());
for (auto i = container.begin(); i != container.end(); ++i) {
iterators.push_back(i);
}
// Sort the keys to ensure consistent persist state.
std::sort(iterators.begin(), iterators.end(),
[](TCItr lhs, TCItr rhs) { return lhs->first < rhs->first; });
doInsert(tag, iterators, inserter, std::false_type{}, std::true_type{});
}
//! Specialisation for std::string, which has iterators but doesn't need
//! to be split up into individual characters
static void dispatch(const std::string& tag,
const std::string& str,
CStatePersistInserter& inserter) {
inserter.insertValue(tag, str);
}
private:
//! Handle the case of a built-in type.
//!
//! \note Type T is not an iterator
template<typename T>
static void doInsert(const std::string& tag,
const T& container,
CStatePersistInserter& inserter,
std::true_type,
std::false_type) {
inserter.insertValue(tag, CPersistUtils::toString(container));
}
//! Handle the case for a non-built-in type, which will be added as a new level.
//!
//! \note Type T is not an iterator
template<typename T>
static void doInsert(const std::string& tag,
const T& container,
CStatePersistInserter& inserter,
std::false_type,
std::false_type) {
inserter.insertLevel(tag, [&container](auto& inserter_) {
newLevel(container.begin(), container.end(), container.size(), inserter_);
});
}
//! Handle the case for a non-built-in type, which will be added as a new level.
//!
//! \note Type T is an iterator
template<typename T>
static void doInsert(const std::string& tag,
const T& t,
CStatePersistInserter& inserter,
std::false_type,
std::true_type) {
using TCItr = boost::indirect_iterator<typename T::const_iterator>;
inserter.insertLevel(tag, [&t](auto& inserter_) {
newLevel(TCItr(t.begin()), TCItr(t.end()), t.size(), inserter_);
});
}
//! Dispatch a collection of items
//!
//! \note The container size is added to allow the restorer to pre-size
//! the new container if appropriate
template<typename ITR>
static void newLevel(ITR begin, ITR end, std::size_t size, CStatePersistInserter& inserter) {
inserter.insertValue(SIZE_TAG, size);
for (; begin != end; ++begin) {
persist(FIRST_TAG, *begin, inserter);
}
}
};
//! \brief Persister for objects which have an acceptPersistInserter method.
template<>
class CPersisterImpl<MemberPersist> {
public:
template<typename T>
static void dispatch(const std::string& tag, const T& t, CStatePersistInserter& inserter) {
inserter.insertLevel(tag,
[&t](auto& inserter_) { newLevel(t, inserter_); });
}
private:
template<typename T>
static void newLevel(const T& t, CStatePersistInserter& inserter) {
t.acceptPersistInserter(inserter);
}
};
//! \brief Persister for objects which have a toDelimited method.
template<>
class CPersisterImpl<MemberToDelimited> {
public:
template<typename T>
static void dispatch(const std::string& tag, const T& t, CStatePersistInserter& inserter) {
inserter.insertValue(tag, t.toDelimited());
}
};
//! \brief Restorer class for PODs and pairs.
template<>
class CRestorerImpl<BasicRestore> {
public:
template<typename T>
static bool dispatch(const std::string& tag, T& t, CStateRestoreTraverser& traverser) {
if (traverser.name() == tag) {
CPersistUtils::CBuiltinFromString stringFunc{CPersistUtils::PAIR_DELIMITER};
return stringFunc(traverser.value(), t);
}
return true;
}
template<typename A, typename B>
static bool
dispatch(const std::string& tag, std::pair<A, B>& t, CStateRestoreTraverser& traverser) {
if (traverser.name() == tag) {
if (traverser.hasSubLevel() == false) {
LOG_ERROR(<< "SubLevel mismatch in restore, at " << traverser.name());
return false;
}
return traverser.traverseSubLevel(
[&t](auto& traverser_) { return subLevel(t, traverser_); });
}
return true;
}
template<typename... T>
static bool
dispatch(const std::string& tag, std::tuple<T...>& t, CStateRestoreTraverser& traverser) {
if (traverser.name() == tag) {
if (traverser.hasSubLevel() == false) {
LOG_ERROR(<< "SubLevel mismatch in restore, at " << traverser.name());
return false;
}
// GCC doesn't handle expanding the logging macro in the lambda context.
std::ostringstream errors;
auto subLevel = [&errors](std::size_t pos, auto& element,
CStateRestoreTraverser& traverser_) {
if (traverser_.name() != TUPLE_PREFIX + std::to_string(pos)) {
errors << "Tag mismatch at " << traverser_.name()
<< ", expected " << TUPLE_PREFIX + std::to_string(pos);
return false;
}
if (restore(traverser_.name(), element, traverser_) == false) {
errors << "Restore error at " << traverser_.name() << ": "
<< traverser_.value();
return false;
}
if (pos + 1 < sizeof...(T)) {
if (traverser_.next() == false) {
errors << "Restore error at " << traverser_.name()
<< ": " << traverser_.value();
return false;
}
}
return true;
};
if (traverser.traverseSubLevel([&](auto& traverser_) {
bool success{true};
std::size_t pos{0};
std::apply(
[&](auto&... element) {
// Note we want to shortcircuit if not successful.
((success = success && subLevel(pos++, element, traverser_)), ...);
},
t);
return success;
}) == false) {
LOG_ERROR(<< errors.str());
return false;
}
}
return true;
}
private:
template<typename A, typename B>
static bool subLevel(std::pair<A, B>& t, CStateRestoreTraverser& traverser) {
if (traverser.name() != FIRST_TAG) {
// Long, meaningful tag names are only ever expected to be used to
// provide rich debug of model state, they are not expected to be present
// in the state from which we perform a restore operation. Hence we pass
// 'false' to the name method of the persistence tag object indicating
// that we wish to use the short tag name.
LOG_ERROR(<< "Tag mismatch at " << traverser.name() << ", expected "
<< FIRST_TAG.name(false));
return false;
}
if (restore(FIRST_TAG, t.first, traverser) == false) {
LOG_ERROR(<< "Restore error at " << traverser.name() << ": "
<< traverser.value());
return false;
}
if (traverser.next() == false) {
LOG_ERROR(<< "Restore error at " << traverser.name() << ": "
<< traverser.value());
return false;
}
if (traverser.name() != SECOND_TAG) {
// Long, meaningful tag names are only ever expected to be used to
// provide rich debug of model state, they are not expected to be present
// in the state from which we perform a restore operation. Hence we pass
// 'false' to the name method of the persistence tag object indicating
// that we wish to use the short tag name.
LOG_ERROR(<< "Tag mismatch at " << traverser.name() << ", expected "
<< SECOND_TAG.name(false));
return false;
}
if (restore(SECOND_TAG, t.second, traverser) == false) {
LOG_ERROR(<< "Restore error at " << traverser.name() << ": "
<< traverser.value());
return false;
}
return true;
}
};
//! \brief Restorer class for collections of items.
template<>
class CRestorerImpl<ContainerRestore> {
public:
template<typename T>
static bool dispatch(const std::string& tag, T& container, CStateRestoreTraverser& traverser) {
return doTraverse(
tag, container, traverser,
std::integral_constant<bool, std::is_arithmetic<typename T::value_type>::value>{});
}
//! Specialisation for std::string, which has iterators but doesn't
//! need to be split up into individual characters
static bool dispatch(const std::string& tag, std::string& str, CStateRestoreTraverser& traverser) {
if (traverser.name() == tag) {
str = traverser.value();
}
return true;
}
private:
struct SSubLevel {
template<typename T>
bool operator()(T& container, CStateRestoreTraverser& traverser) const {
using TValueType = typename remove_const<typename T::value_type>::type;
do {
if (traverser.name() == SIZE_TAG) {
std::size_t size{0};
if (core::CStringUtils::stringToType(traverser.value(), size) == false) {
LOG_WARN(<< "Failed to determine size: " << traverser.value());
} else {
reserve(container, size);
}
} else {
TValueType value;
if (restore(FIRST_TAG, value, traverser) == false) {
LOG_ERROR(<< "Restoration error at " << traverser.name());
return false;
}
container.insert(container.end(), std::move(value));
}
} while (traverser.next());
return true;
}
template<typename T, std::size_t N>
bool operator()(std::array<T, N>& container, CStateRestoreTraverser& traverser) const {
using TValueType = typename remove_const<T>::type;
auto i = container.begin();
do {
TValueType value;
if (traverser.name() == FIRST_TAG) {
if (restore(FIRST_TAG, value, traverser) == false) {
LOG_ERROR(<< "Restoration error at " << traverser.name());
return false;
}
if (i == container.end()) {
LOG_ERROR(<< "Too many values for size " << N
<< " array during restoration");
return false;
}
*(i++) = std::move(value);
}
} while (traverser.next());
return true;
}
};
private:
template<typename T>
static bool doTraverse(const std::string& tag,
T& container,
CStateRestoreTraverser& traverser,
std::true_type) {
if (traverser.name() == tag) {
return CPersistUtils::fromString(traverser.value(), container);
}
return true;
}
template<typename T>
static bool doTraverse(const std::string& tag,
T& container,
CStateRestoreTraverser& traverser,
std::false_type) {
if (traverser.name() == tag) {
if (traverser.hasSubLevel() == false) {
LOG_ERROR(<< "SubLevel mismatch in restore at " << traverser.name());
return false;
}
return traverser.traverseSubLevel(
[&container, subLevel = SSubLevel{} ](auto& traverser_) {
return subLevel(container, traverser_);
});
}
return true;
}
};
//! \brief Restorer for objects which has an acceptRestoreTraverser method.
template<>
class CRestorerImpl<MemberRestore> {
public:
template<typename T>
static bool dispatch(const std::string& tag, T& t, CStateRestoreTraverser& traverser) {
if (traverser.name() == tag) {
if (traverser.hasSubLevel() == false) {
LOG_ERROR(<< "SubLevel mismatch in restore at " << traverser.name());
return false;
}
return traverser.traverseSubLevel(
[&t](auto& traverser_) { return subLevel(t, traverser_); });
}
return true;
}
private:
template<typename T>
static bool subLevel(T& t, CStateRestoreTraverser& traverser) {
return t.acceptRestoreTraverser(traverser);
}
};
//! \brief Restorer for objects which have a fromDelimited method.
template<>
class CRestorerImpl<MemberFromDelimited> {
public:
template<typename T>
static bool dispatch(const std::string& /*tag*/, T& t, CStateRestoreTraverser& traverser) {
return t.fromDelimited(traverser.value());
}
};
} // persist_utils_detail::
}
}
#endif // INCLUDED_ml_core_CPersistUtils_h