From 3be2e1b28fb76ce303d8338ff79c0015c9c45bd1 Mon Sep 17 00:00:00 2001 From: Benjamin Navarro Date: Thu, 16 Jul 2020 17:26:21 +0200 Subject: [PATCH] Allowing partial specialization of the convert struct This is done by adding a second, defaulted, template parameter that can be used in conjunction of std::enable_if An example of usage is added to the tutorial --- docs/Tutorial.md | 76 +++++++++++++++ include/yaml-cpp/node/convert.h | 2 +- include/yaml-cpp/node/node.h | 2 +- .../convert_partial_specialization_test.cpp | 92 +++++++++++++++++++ 4 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 test/integration/convert_partial_specialization_test.cpp diff --git a/docs/Tutorial.md b/docs/Tutorial.md index a7b0e21..ec1c7cb 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -198,4 +198,80 @@ Then you could use `Vec3` wherever you could use any other type: YAML::Node node = YAML::Load("start: [1, 3, 0]"); Vec3 v = node["start"].as(); node["end"] = Vec3(2, -1, 0); +``` + +## Partial specialization + +If you need to specialize the `convert` struct for a set of types instead of just one you can use partial specialization with the help of `std::enable_if` (SFINAE). + +Here is a small example showing how to partially specialize the `convert` struct for all types deriving from a base class: + +```cpp +// Base class +class A +{ +public: + A() = default; + A(int a) : a{a} {} + + // virtual load/emit methods + virtual void load(const YAML::Node &node) { + a = node["a"].as(); + } + + virtual YAML::Node emit() const { + YAML::Node node; + node["a"] = a; + return node; + } + + int a; +}; + +// Derived class +class B : public A +{ +public: + B() = default; + B(int a, int b) : A{a}, b{b} {} + + // override virtual load/emit methods + virtual void load(const YAML::Node &node) override { + A::load(node); + b = node["b"].as(); + } + + virtual YAML::Node emit() const override { + YAML::Node node = A::emit(); + node["b"] = b; + return node; + } + + int b; +}; + +// Implementation of convert::{encode,decode} for all classes derived from or being A +namespace YAML { + template + struct convert::value>::type> { + static Node encode(const T &rhs) { + Node node = rhs.emit(); + return node; + } + + static bool decode(const Node &node, T &rhs) { + rhs.load(node); + return true; + } + }; +} +``` + +Which can then be use like this: +```cpp +YAML::Node node = YAML::Load("{a: 1, b: 2}"); +B b = node.as(); +b.a = 12; +b.b = 42; +node = b; ``` \ No newline at end of file diff --git a/include/yaml-cpp/node/convert.h b/include/yaml-cpp/node/convert.h index 596898d..cb9e421 100644 --- a/include/yaml-cpp/node/convert.h +++ b/include/yaml-cpp/node/convert.h @@ -27,7 +27,7 @@ namespace YAML { class Binary; struct _Null; -template +template struct convert; } // namespace YAML diff --git a/include/yaml-cpp/node/node.h b/include/yaml-cpp/node/node.h index c9e9a0a..80c9f41 100644 --- a/include/yaml-cpp/node/node.h +++ b/include/yaml-cpp/node/node.h @@ -141,7 +141,7 @@ YAML_CPP_API bool operator==(const Node& lhs, const Node& rhs); YAML_CPP_API Node Clone(const Node& node); -template +template struct convert; } diff --git a/test/integration/convert_partial_specialization_test.cpp b/test/integration/convert_partial_specialization_test.cpp new file mode 100644 index 0000000..4ab07fe --- /dev/null +++ b/test/integration/convert_partial_specialization_test.cpp @@ -0,0 +1,92 @@ +#include "yaml-cpp/emitterstyle.h" +#include "yaml-cpp/eventhandler.h" +#include "yaml-cpp/yaml.h" // IWYU pragma: keep +#include "gtest/gtest.h" + +// Base class +class A { + public: + A() = default; + A(int a) : a{a} {} + + // virtual load/emit methods + virtual void load(const YAML::Node &node) { a = node["a"].as(); } + + virtual YAML::Node emit() const { + YAML::Node node; + node["a"] = a; + return node; + } + + int a{}; +}; + +// Derived class +class B : public A { + public: + B() = default; + B(int a, int b) : A{a}, b{b} {} + + // override virtual load/emit methods + virtual void load(const YAML::Node &node) override { + A::load(node); + b = node["b"].as(); + } + + virtual YAML::Node emit() const override { + YAML::Node node = A::emit(); + node["b"] = b; + return node; + } + + int b{}; +}; + +// Implementation of convert::{encode,decode} for all classes derived from or +// being A +namespace YAML { +template +struct convert::value>::type> { + static Node encode(const T &rhs) { + Node node = rhs.emit(); + return node; + } + + static bool decode(const Node &node, T &rhs) { + rhs.load(node); + return true; + } +}; + +namespace { + +TEST(ConvertPartialSpecializationTest, EncodeBaseClass) { + Node n(Load("{a: 1}")); + A a = n.as(); + EXPECT_EQ(a.a, 1); +} + +TEST(ConvertPartialSpecializationTest, EncodeDerivedClass) { + Node n(Load("{a: 1, b: 2}")); + B b = n.as(); + EXPECT_EQ(b.a, 1); + EXPECT_EQ(b.b, 2); +} + +TEST(ConvertPartialSpecializationTest, DecodeBaseClass) { + A a(1); + Node n; + n = a; + EXPECT_EQ(a.a, n["a"].as()); +} + +TEST(ConvertPartialSpecializationTest, DecodeDerivedClass) { + B b(1, 2); + Node n; + n = b; + EXPECT_EQ(b.a, n["a"].as()); + EXPECT_EQ(b.b, n["b"].as()); +} + +} // namespace +} // namespace YAML \ No newline at end of file