diff --git a/docs/Tutorial.md b/docs/Tutorial.md index ba44d67..5db31e9 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -198,5 +198,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/node/detail/impl.h b/include/yaml-cpp/node/detail/impl.h index 19dc426..ab5d8c3 100644 --- a/include/yaml-cpp/node/detail/impl.h +++ b/include/yaml-cpp/node/detail/impl.h @@ -96,30 +96,84 @@ 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) { + try { - const auto rslt = convert::decode(Node(*this, pMemory)); - return rslt == rhs; - } catch (const conversion::DecodeException& e) { - return false; - } catch(...) { - std::rethrow_exception(std::current_exception()); +#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; } } +#undef PRE_CPP17_SHIM + inline bool node::equals(const char* rhs, shared_memory_holder pMemory) { - try { - const auto rslt = - convert::decode(Node(*this, std::move(pMemory))); - return rslt == rhs; - } catch (const conversion::DecodeException& e) { - return false; - } catch(...) { - std::rethrow_exception(std::current_exception()); + std::string lhs; + if (convert::decode(Node(*this, std::move(pMemory)), lhs)) { + return lhs == rhs; } + 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 587f4c5..46917da 100644 --- a/include/yaml-cpp/node/impl.h +++ b/include/yaml-cpp/node/impl.h @@ -14,7 +14,7 @@ #include "yaml-cpp/node/node.h" #include #include - +#include namespace YAML { inline Node::Node() @@ -88,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 { @@ -99,13 +137,25 @@ struct as_if { return fallback; try { - return convert::decode(node); +#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 (...) { - std::rethrow_exception(std::current_exception()); + throw; } - } + }; }; //specialize for Node @@ -125,7 +175,7 @@ struct as_if { } catch (const conversion::DecodeException& e) { return fallback; } catch (...) { - std::rethrow_exception(std::current_exception()); + throw; } } }; @@ -154,14 +204,27 @@ struct as_if { throw TypedBadConversion(node.Mark()); try { - return convert::decode(node); +#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()); + throw TypedBadConversion(node.Mark()); } catch (...) { - std::rethrow_exception(std::current_exception()); + throw; } }; }; +#undef PRE_CPP17_SHIM template <> struct as_if { diff --git a/include/yaml-cpp/node/node.h b/include/yaml-cpp/node/node.h index 634f09d..602036f 100644 --- a/include/yaml-cpp/node/node.h +++ b/include/yaml-cpp/node/node.h @@ -143,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) {