Skip to content
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
12 changes: 11 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ cmake_dependent_option(YAML_MSVC_SHARED_RT
"CMAKE_SYSTEM_NAME MATCHES Windows" OFF)
set(YAML_CPP_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/yaml-cpp"
CACHE STRING "Path to install the CMake package to")

if (YAML_CPP_FORMAT_SOURCE)
find_program(YAML_CPP_CLANG_FORMAT_EXE NAMES clang-format)
endif()
Expand Down Expand Up @@ -143,6 +143,16 @@ write_basic_package_version_file(

configure_file(yaml-cpp.pc.in yaml-cpp.pc @ONLY)

set(YAML_CPP_USE_OPTIONAL_DEFAULT OFF)
get_target_property(YAML_CPP_CXX_STANDARD yaml-cpp CXX_STANDARD)
if ("${YAML_CPP_CXX_STANDARD}" VERSION_GREATER_EQUAL 17)
set(YAML_CPP_USE_OPTIONAL_DEFAULT ON)
endif()
option(YAML_CPP_USE_OPTIONAL "Support for non-default constructible 'convert` overloads (requires c++17 and newer)" "${YAML_CPP_USE_OPTIONAL_DEFAULT}")
if (YAML_CPP_USE_OPTIONAL)
target_compile_definitions(yaml-cpp PUBLIC YAML_CPP_USE_OPTIONAL)
endif()

if (YAML_CPP_INSTALL)
install(TARGETS yaml-cpp
EXPORT yaml-cpp-targets
Expand Down
38 changes: 35 additions & 3 deletions docs/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,38 @@ Vec3 v = node["start"].as<Vec3>();
node["end"] = Vec3(2, -1, 0);
```

## Non-default constructible types (requires c++17 and newer)
Yaml-cpp also supports types that are not default constructible. For this one need to specialize `YAML::convert<std::optional<>>`.
Assuming you have:

```cpp
class Vec3 {
double x, y, z;
public:
Vec3(double x, double y, double z} : x{x}, y{y}, z{z} {}
};
```

you could write (for encoding the previous `convert<Vec3>` with the `encode` method is required)

```cpp
namespace YAML {
template<>
struct convert<std::optional<Vec3>> {
static bool decode(const Node& node, std::optional<Vec3>& rhs) {
if(!node.IsSequence() || node.size() != 3) {
return false;
}
rhs.emplace(
node[0].as<double>(),
node[1].as<double>(),
node[2].as<double>()
);
return true;
}
};
}

## 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).
Expand All @@ -224,7 +256,7 @@ public:
node["a"] = a;
return node;
}

int a;
};

Expand Down Expand Up @@ -252,7 +284,7 @@ public:

// Implementation of convert::{encode,decode} for all classes derived from or being A
namespace YAML {
template<typename T>
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();
Expand All @@ -274,4 +306,4 @@ B b = node.as<B>();
b.a = 12;
b.b = 42;
node = b;
```
```
71 changes: 65 additions & 6 deletions include/yaml-cpp/node/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
#include <sstream>
#include <string>

// Check YAML_CPP_USE_OPTIONAL is set and language standard provides supports
// if available and supported include required header
// otherwise remove YAML_CPP_USE_OPTIONAL definition
#ifdef YAML_CPP_USE_OPTIONAL
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#include <optional>
#else
#undef YAML_CPP_USE_OPTIONAL
#endif
#endif

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

// template helpers
#ifdef YAML_CPP_USE_OPTIONAL
template <typename T>
struct decode_box : std::optional<T> {
decode_box() = default;
template <typename S>
decode_box(S&&) {}
};

template <typename T>
struct convert<decode_box<T>> {
static bool decode(const Node& node, decode_box<T>& rhs) {
return convert<std::optional<T>>::decode(node, rhs);
}
};
template <typename T>
struct convert<std::optional<T>> {
static bool decode(const Node& node, std::optional<T>& rhs) {
if (node.IsNull()) {
return false;
}
rhs.emplace();
if (!convert<T>::decode(node, *rhs)) {
rhs.reset();
return false;
}
return true;
}
};


#else

template <typename T>
struct decode_box {
T t;
T& operator*() {
return t;
}
};
template <typename T>
struct convert<decode_box<T>> {
static bool decode(const Node& node, decode_box<T>& rhs) {
return convert<T>::decode(node, *rhs);
}
};

#endif

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

T t = fallback;
if (convert<T>::decode(node, t))
return t;
decode_box<T> t{fallback};
if (convert<decltype(t)>::decode(node, t))
return *t;
return fallback;
}
};
Expand Down Expand Up @@ -133,9 +192,9 @@ struct as_if<T, void> {
if (!node.m_pNode) // no fallback
throw InvalidNode(node.m_invalidKey);

T t;
if (convert<T>::decode(node, t))
return t;
decode_box<T> t;
if (convert<decltype(t)>::decode(node, t))
return *t;
throw TypedBadConversion<T>(node.Mark());
}
};
Expand Down
76 changes: 76 additions & 0 deletions test/node/node_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ template <class K, class V, class C=std::less<K>> using CustomMap = std::map<K,V
template <class K, class V, class H=std::hash<K>, class P=std::equal_to<K>> using CustomUnorderedMap = std::unordered_map<K,V,H,P,CustomAllocator<std::pair<const K,V>>>;
template <class K, class H=std::hash<K>, class P=std::equal_to<K>> using CustomUnorderedSet = std::unordered_set<K,H,P,CustomAllocator<K>>;

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_USE_OPTIONAL
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 @@ -57,6 +74,43 @@ 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_USE_OPTIONAL
template <>
struct convert<std::optional<NonDefCtorVec3>> {
static bool decode(const Node& node, std::optional<NonDefCtorVec3>& rhs) {
if (!node.IsSequence() || node.size() != 3) {
return false;
}
rhs.emplace(node[0].as<double>(), node[1].as<double>(), node[2].as<double>());

return true;
}
};
#endif

namespace {
TEST(NodeTest, SimpleScalar) {
Node node = Node("Hello, World!");
Expand Down Expand Up @@ -911,6 +965,28 @@ TEST(NodeTest, CreateMapWithFloatingPoint0Key) {
EXPECT_TRUE(node.IsMap());
}

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_USE_OPTIONAL
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