From 433689e510e147f856a46e3b3e50001213589c2b Mon Sep 17 00:00:00 2001 From: marcel Date: Thu, 3 Mar 2022 11:03:33 +0100 Subject: [PATCH] a working formulation integrating a new API for non-default constructable custom types, while seamlessly supporting the old API; see Tutorial.mp for details --- docs/Tutorial.md | 49 +++++++++++- include/yaml-cpp/exceptions.h | 9 +++ include/yaml-cpp/node/detail/impl.h | 69 +++++++++++++++- include/yaml-cpp/node/impl.h | 120 +++++++++++++++++++++++++--- include/yaml-cpp/node/node.h | 3 + test/node/node_test.cpp | 81 +++++++++++++++++++ 6 files changed, 316 insertions(+), 15 deletions(-) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index a7b0e21..5654a51 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -197,5 +197,50 @@ Then you could use `Vec3` wherever you could use any other type: ```cpp YAML::Node node = YAML::Load("start: [1, 3, 0]"); Vec3 v = node["start"].as(); -node["end"] = Vec3(2, -1, 0); -``` \ No newline at end of file +node["end"] = Vec3{2, -1, 0}; +``` + +Observe that in the above example the custom type is, decalred as +a struct, explicit default constructable and all its members are +exposed. For non default constructable types like + +```cpp +class NonDefCtorVec3 : public Vec3 { + using Vec3::x; + using Vec3::y; + using Vec3::z; + public: + NonDefCtorVec3(double x, double y, double z) + : Vec3() { this->x=x; this->y=y; this->z=z; + }; +}; +``` +a new API is available, that freshens up the signature of the 'convert::decode' +method and introduces the abortion of the deserialization process by throwing +an `DecodeException`. + +```cpp +namespace YAML { +template <> +struct convert { + static Node encode(const NonDefCtorVec3& rhs) { + return convert::encode(rhs); + } + + static NonDefCtorVec3 decode(const Node& node) { + if (!node.IsSequence() || node.size() != 3) { + throw YAML::conversion::DecodeException(); + } + return {node[0].as(), node[1].as(), node[2].as()}; + } +}; +} +``` + +The behavior is exactly the same + +```cpp +YAML::Node node = YAML::Load("start: [1, 3, 0]"); +NonDefCtorVec3 v = node["start"].as(); +node["end"] = NonDefCtorVec3(2, -1, 0); +``` diff --git a/include/yaml-cpp/exceptions.h b/include/yaml-cpp/exceptions.h index f6b2602..4788418 100644 --- a/include/yaml-cpp/exceptions.h +++ b/include/yaml-cpp/exceptions.h @@ -298,6 +298,15 @@ class YAML_CPP_API BadFile : public Exception { BadFile(const BadFile&) = default; ~BadFile() YAML_CPP_NOEXCEPT override; }; + + +namespace conversion{ + class DecodeException : public std::runtime_error { + public: + DecodeException(const std::string& s="") : std::runtime_error(s) {}; + }; +} + } // namespace YAML #endif // EXCEPTIONS_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/include/yaml-cpp/node/detail/impl.h b/include/yaml-cpp/node/detail/impl.h index b38038d..ab5d8c3 100644 --- a/include/yaml-cpp/node/detail/impl.h +++ b/include/yaml-cpp/node/detail/impl.h @@ -96,14 +96,72 @@ struct remove_idx +struct static_switch; + +template<> //new api +struct static_switch { + template + static T call(const Node& node) { + return convert::decode(node); + } +}; + +template<> //old api +struct static_switch { + template + static T call(const Node& node) { + T t; + if (convert::decode(node, t)) + return t; + throw conversion::DecodeException(); + } +}; +#endif + +//detect the method of the new api +template +std::false_type has_decode_new_api(long); + +template +auto has_decode_new_api(int) + -> decltype( T::decode(std::declval()), std::true_type{}); + + + template inline bool node::equals(const T& rhs, shared_memory_holder pMemory) { - T lhs; - if (convert::decode(Node(*this, pMemory), lhs)) { - return lhs == rhs; + + try { +#ifdef PRE_CPP17_SHIM + return static_switch>( + 0))::value>::template call(Node(*this, pMemory)) == rhs; +#else + if constexpr (decltype(has_decode_new_api>(0))::value >) + return convert::decode(Node(*this, pMemory)) == rhs; + else { + T lhs; + if (convert::decode(Node(*this, pMemory), lhs)) + return lhs == rhs; + throw conversion::DecodeException(); + } +#endif + } catch(const conversion::DecodeException& e) { + //throw; //prefer to throw over returning just the inability to deserialize + return false; //not doing this breaks upstream functionality + } catch (...) { + throw; } - return false; } +#undef PRE_CPP17_SHIM + inline bool node::equals(const char* rhs, shared_memory_holder pMemory) { std::string lhs; @@ -113,6 +171,9 @@ inline bool node::equals(const char* rhs, shared_memory_holder pMemory) { return false; } + + + // indexing template inline node* node_data::get(const Key& key, diff --git a/include/yaml-cpp/node/impl.h b/include/yaml-cpp/node/impl.h index 312281f..46917da 100644 --- a/include/yaml-cpp/node/impl.h +++ b/include/yaml-cpp/node/impl.h @@ -14,6 +14,7 @@ #include "yaml-cpp/node/node.h" #include #include +#include namespace YAML { inline Node::Node() @@ -87,6 +88,44 @@ inline NodeType::value Node::Type() const { // access +//detect the method of the new api +template +std::false_type has_decode_new_api(long); + +template +auto has_decode_new_api(int) + -> decltype( T::decode(std::declval()), std::true_type{}); + +//shim to emulate constexpr-if, which is a feature of c++17 +#if __cplusplus < 201703L || (defined(_MSVC_LANG) && _MSVC_LANG < 201703L) +#define PRE_CPP17_SHIM +#endif + +#ifdef PRE_CPP17_SHIM +template +struct static_switch; + +template<> //new api +struct static_switch { + template + static T call(const Node& node) { + return convert::decode(node); + } +}; + +template<> //old api +struct static_switch { + template + static T call(const Node& node) { + T t; + if (convert::decode(node, t)) + return t; + throw conversion::DecodeException(); + } +}; +#endif + + // template helpers template struct as_if { @@ -97,10 +136,47 @@ struct as_if { if (!node.m_pNode) return fallback; - T t; - if (convert::decode(node, t)) - return t; - return fallback; + try { +#ifdef PRE_CPP17_SHIM + return static_switch>( + 0))::value>::template call(node); +#else + if constexpr (decltype(has_decodex>(0))::value >) + return convert::decodex(node); + else { + T t; + if (convert::decode(node, t)) + return t; + throw conversion::DecodeException(); + } +#endif + } catch (const conversion::DecodeException& e) { + return fallback; + } catch (...) { + throw; + } + }; +}; + +//specialize for Node +template +struct as_if { + explicit as_if(const Node& node_) : node(node_) {} + const Node& node; + + Node operator()(const S& fallback) const { + if (!node.m_pNode) + return fallback; + + try { + Node n; + n.reset(node); + return node; + } catch (const conversion::DecodeException& e) { + return fallback; + } catch (...) { + throw; + } } }; @@ -127,12 +203,28 @@ struct as_if { if (!node.m_pNode) throw TypedBadConversion(node.Mark()); - T t; - if (convert::decode(node, t)) - return t; - throw TypedBadConversion(node.Mark()); - } + try { +#ifdef PRE_CPP17_SHIM + return static_switch>( + 0))::value>::template call(node); +#else + if constexpr (decltype(has_decodex>(0))::value >) + return convert::decodex(node); + else { + T t; + if (convert::decode(node, t)) + return t; + throw conversion::DecodeException(); + } +#endif + } catch(const conversion::DecodeException& e) { + throw TypedBadConversion(node.Mark()); + } catch (...) { + throw; + } + }; }; +#undef PRE_CPP17_SHIM template <> struct as_if { @@ -321,6 +413,16 @@ std::string key_to_string(const Key& key) { } // indexing +template +inline bool Node::ContainsKey(const Key& key) const { + EnsureNodeExists(); + if (! IsMap()) + return false; + detail::node* value = + static_cast(*m_pNode).get(key, m_pMemory); + return (bool)value; +} + template inline const Node Node::operator[](const Key& key) const { EnsureNodeExists(); diff --git a/include/yaml-cpp/node/node.h b/include/yaml-cpp/node/node.h index c9e9a0a..602036f 100644 --- a/include/yaml-cpp/node/node.h +++ b/include/yaml-cpp/node/node.h @@ -99,6 +99,8 @@ class YAML_CPP_API Node { // indexing template + bool ContainsKey(const Key& key) const; + template const Node operator[](const Key& key) const; template Node operator[](const Key& key); @@ -141,6 +143,7 @@ YAML_CPP_API bool operator==(const Node& lhs, const Node& rhs); YAML_CPP_API Node Clone(const Node& node); +//forward declare this customization point for all types template struct convert; } diff --git a/test/node/node_test.cpp b/test/node/node_test.cpp index d4367c5..6dd4ba7 100644 --- a/test/node/node_test.cpp +++ b/test/node/node_test.cpp @@ -42,6 +42,30 @@ template using CustomList = std::list>; template > using CustomMap = std::map>>; template , class P=std::equal_to> using CustomUnorderedMap = std::unordered_map>>; +struct Vec3 { + double x, y, z; + bool operator==(const Vec3& rhs) const { + return x == rhs.x && y == rhs.y && z == rhs.z; + } +}; + +class NonDefCtorVec3 : public Vec3 { + using Vec3::x; + using Vec3::y; + using Vec3::z; + public: + NonDefCtorVec3(double x, double y, double z) + : Vec3() { + this->x=x; + this->y=y; + this->z=z; + }; + bool operator==(const NonDefCtorVec3& rhs) const { + return x == rhs.x && y == rhs.y && z == rhs.z; + } +}; + + } // anonymous namespace using ::testing::AnyOf; @@ -56,6 +80,45 @@ using ::testing::Eq; } namespace YAML { + +//define custom convert structs +template<> +struct convert { + static Node encode(const Vec3& rhs) { + Node node; + node.push_back(rhs.x); + node.push_back(rhs.y); + node.push_back(rhs.z); + return node; + } + + static bool decode(const Node& node, Vec3& rhs) { + if(!node.IsSequence() || node.size() != 3) { + return false; + } + + rhs.x = node[0].as(); + rhs.y = node[1].as(); + rhs.z = node[2].as(); + return true; + } +}; + +template <> +struct convert { + static Node encode(const NonDefCtorVec3& rhs) { + return convert::encode(rhs); + } + + static NonDefCtorVec3 decode(const Node& node) { + if (!node.IsSequence() || node.size() != 3) { + throw YAML::conversion::DecodeException(); + } + return {node[0].as(), node[1].as(), node[2].as()}; + } +}; + + namespace { TEST(NodeTest, SimpleScalar) { Node node = Node("Hello, World!"); @@ -674,6 +737,24 @@ TEST(NodeTest, AccessNonexistentKeyOnConstNode) { ASSERT_FALSE(other["5"]); } +TEST(NodeTest, CustomClassDecoding) { + YAML::Node node; + node.push_back(1.0); + node.push_back(2.0); + node.push_back(3.0); + ASSERT_TRUE(node.IsSequence()); + EXPECT_EQ(node.as(), (Vec3{1.0, 2.0, 3.0})); +} + +TEST(NodeTest, CustomNonDefaultConstructibleClassDecoding) { + YAML::Node node; + node.push_back(1.0); + node.push_back(2.0); + node.push_back(3.0); + ASSERT_TRUE(node.IsSequence()); + EXPECT_EQ(node.as(), (NonDefCtorVec3{1.0, 2.0, 3.0})); +} + class NodeEmitterTest : public ::testing::Test { protected: void ExpectOutput(const std::string& output, const Node& node) {