From 8eef18882e06682f12d9830bfed0c9fe97dc6d65 Mon Sep 17 00:00:00 2001 From: Omer Ozarslan Date: Thu, 15 Jul 2021 09:41:08 -0500 Subject: [PATCH 1/2] Allow decoding types without a default constructor `YAML::decode_dispatcher::dispatch` is used for decoding `YAML::Node` to a type T when compiled with C++17 or newer. This is a backward compatible change as the default library implementation calls `YAML::convert::decode`. An example: ```cpp struct Vec3 { double x, y, z; Vec3(double x, double y, double z) : x(x), y(y), z(z) {} // ... }; namespace YAML { template<> struct decode_dispatcher { static std::optional dispatch(const Node& node) { if(!node.IsSequence() || node.size() != 3) return std::nullopt; return Vec3( node[0].as(), node[1].as(), node[2].as() ); } }; } ``` --- include/yaml-cpp/node/impl.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/include/yaml-cpp/node/impl.h b/include/yaml-cpp/node/impl.h index 312281f18..e41527565 100644 --- a/include/yaml-cpp/node/impl.h +++ b/include/yaml-cpp/node/impl.h @@ -15,6 +15,11 @@ #include #include +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#include +#define YAML_CPP_HAS_DECODE_DISPATCHER +#endif + namespace YAML { inline Node::Node() : m_isValid(true), m_invalidKey{}, m_pMemory(nullptr), m_pNode(nullptr) {} @@ -88,6 +93,18 @@ inline NodeType::value Node::Type() const { // access // template helpers +#ifdef YAML_CPP_HAS_DECODE_DISPATCHER +template +struct decode_dispatcher { + static std::optional dispatch(const Node& node) { + T t; + if (convert::decode(node, t)) + return {t}; + return std::nullopt; + } +}; +#endif + template struct as_if { explicit as_if(const Node& node_) : node(node_) {} @@ -97,10 +114,14 @@ struct as_if { if (!node.m_pNode) return fallback; +#ifdef YAML_CPP_HAS_DECODE_DISPATCHER + return decode_dispatcher::dispatch(node).value_or(fallback); +#else T t; if (convert::decode(node, t)) return t; return fallback; +#endif } }; @@ -127,9 +148,15 @@ struct as_if { if (!node.m_pNode) throw TypedBadConversion(node.Mark()); +#ifdef YAML_CPP_HAS_DECODE_DISPATCHER + auto t = decode_dispatcher::dispatch(node); + if (t) + return *t; +#else T t; if (convert::decode(node, t)) return t; +#endif throw TypedBadConversion(node.Mark()); } }; From a54ac25254acb3bab0d3a0bd2225fcb07c705d67 Mon Sep 17 00:00:00 2001 From: Omer Ozarslan Date: Thu, 15 Jul 2021 09:41:45 -0500 Subject: [PATCH 2/2] Add tests for custom decoding --- test/node/node_test.cpp | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/node/node_test.cpp b/test/node/node_test.cpp index 4f577c846..87252664a 100644 --- a/test/node/node_test.cpp +++ b/test/node/node_test.cpp @@ -41,6 +41,23 @@ template using CustomVector = std::vector>; template using CustomList = std::list>; template > using CustomMap = std::map>>; +struct Vec3 { + double x, y, z; + bool operator==(const Vec3& rhs) const { + return x == rhs.x && y == rhs.y && z == rhs.z; + } +}; + +#ifdef YAML_CPP_HAS_DECODE_DISPATCHER +struct NonDefCtorVec3 { + double x, y, z; + NonDefCtorVec3(double x, double y, double z) : x(x), y(y), z(z) {} + bool operator==(const NonDefCtorVec3& rhs) const { + return x == rhs.x && y == rhs.y && z == rhs.z; + } +}; +#endif + } // anonymous namespace using ::testing::AnyOf; @@ -55,6 +72,41 @@ using ::testing::Eq; } namespace YAML { + +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; + } +}; + +#ifdef YAML_CPP_HAS_DECODE_DISPATCHER +template <> +struct decode_dispatcher { + static std::optional dispatch(const Node& node) { + if (!node.IsSequence() || node.size() != 3) { + return std::nullopt; + } + return {{node[0].as(), node[1].as(), node[2].as()}}; + } +}; +#endif + namespace { TEST(NodeTest, SimpleScalar) { Node node = Node("Hello, World!"); @@ -645,6 +697,28 @@ 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) { +#ifdef YAML_CPP_HAS_DECODE_DISPATCHER + 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})); +#else + GTEST_SKIP() << "Compile with C++17 for customizing non-default-constructible custom types."; +#endif +} + class NodeEmitterTest : public ::testing::Test { protected: void ExpectOutput(const std::string& output, const Node& node) {