Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing partial specialization of the convert struct #922

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions docs/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec3>();
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
dota17 marked this conversation as resolved.
Show resolved Hide resolved
{
public:
A() = default;
A(int a) : a{a} {}

// virtual load/emit methods
virtual void load(const YAML::Node &node) {
a = node["a"].as<int>();
}

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<int>();
}

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<typename T>
struct convert<T, typename std::enable_if<std::is_base_of<A, T>::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>();
b.a = 12;
b.b = 42;
node = b;
```
2 changes: 1 addition & 1 deletion include/yaml-cpp/node/convert.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
namespace YAML {
class Binary;
struct _Null;
template <typename T>
template <typename T, typename Enable>
struct convert;
} // namespace YAML

Expand Down
2 changes: 1 addition & 1 deletion include/yaml-cpp/node/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ YAML_CPP_API bool operator==(const Node& lhs, const Node& rhs);

YAML_CPP_API Node Clone(const Node& node);

template <typename T>
template <typename T, typename Enable = void>
struct convert;
}

Expand Down
92 changes: 92 additions & 0 deletions test/integration/convert_partial_specialization_test.cpp
Original file line number Diff line number Diff line change
@@ -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<int>(); }

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though it's a test case, I think the constructors are needed, like #310. It is also convenient for later usage.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about updating the Tutorial.md at the same time. And use this in testcase.

  A a(1);
  B b(1, 2);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the test cases to use the constructors and added them in the tutorial.
However the tutorial doesn't make use of them

A::load(node);
b = node["b"].as<int>();
}

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 <typename T>
struct convert<T, typename std::enable_if<std::is_base_of<A, T>::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<A>();
EXPECT_EQ(a.a, 1);
}

TEST(ConvertPartialSpecializationTest, EncodeDerivedClass) {
Node n(Load("{a: 1, b: 2}"));
B b = n.as<B>();
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<int>());
}

TEST(ConvertPartialSpecializationTest, DecodeDerivedClass) {
B b(1, 2);
Node n;
n = b;
EXPECT_EQ(b.a, n["a"].as<int>());
EXPECT_EQ(b.b, n["b"].as<int>());
}

} // namespace
} // namespace YAML