Skip to content

Implement a customization point for types not default constructible #1010

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

Open
wants to merge 2 commits 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
27 changes: 27 additions & 0 deletions include/yaml-cpp/node/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
#include <sstream>
#include <string>

#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#include <optional>
#define YAML_CPP_HAS_DECODE_DISPATCHER
#endif

namespace YAML {
inline Node::Node()
: m_isValid(true), m_invalidKey{}, m_pMemory(nullptr), m_pNode(nullptr) {}
Expand Down Expand Up @@ -88,6 +93,18 @@ inline NodeType::value Node::Type() const {
// access

// template helpers
#ifdef YAML_CPP_HAS_DECODE_DISPATCHER
template <typename T, typename Enable = void>
struct decode_dispatcher {
static std::optional<T> dispatch(const Node& node) {
T t;
if (convert<T>::decode(node, t))
return {t};
return std::nullopt;
}
};
#endif

template <typename T, typename S>
struct as_if {
explicit as_if(const Node& node_) : node(node_) {}
Expand All @@ -97,10 +114,14 @@ struct as_if {
if (!node.m_pNode)
return fallback;

#ifdef YAML_CPP_HAS_DECODE_DISPATCHER
return decode_dispatcher<T>::dispatch(node).value_or(fallback);
#else
T t;
if (convert<T>::decode(node, t))
return t;
return fallback;
#endif
}
};

Expand All @@ -127,9 +148,15 @@ struct as_if<T, void> {
if (!node.m_pNode)
throw TypedBadConversion<T>(node.Mark());

#ifdef YAML_CPP_HAS_DECODE_DISPATCHER
auto t = decode_dispatcher<T>::dispatch(node);
if (t)
return *t;
#else
T t;
if (convert<T>::decode(node, t))
return t;
#endif
throw TypedBadConversion<T>(node.Mark());
}
};
Expand Down
74 changes: 74 additions & 0 deletions test/node/node_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ template <class T> using CustomVector = std::vector<T,CustomAllocator<T>>;
template <class T> using CustomList = std::list<T,CustomAllocator<T>>;
template <class K, class V, class C=std::less<K>> using CustomMap = std::map<K,V,C,CustomAllocator<std::pair<const K,V>>>;

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;
Expand All @@ -55,6 +72,41 @@ using ::testing::Eq;
}

namespace YAML {

template<>
struct convert<Vec3> {
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<double>();
rhs.y = node[1].as<double>();
rhs.z = node[2].as<double>();
return true;
}
};

#ifdef YAML_CPP_HAS_DECODE_DISPATCHER
template <>
struct decode_dispatcher<NonDefCtorVec3> {
static std::optional<NonDefCtorVec3> dispatch(const Node& node) {
if (!node.IsSequence() || node.size() != 3) {
return std::nullopt;
}
return {{node[0].as<double>(), node[1].as<double>(), node[2].as<double>()}};
}
};
#endif

namespace {
TEST(NodeTest, SimpleScalar) {
Node node = Node("Hello, World!");
Expand Down Expand Up @@ -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>(), (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>(), (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) {
Expand Down