a working formulation integrating a new API for non-default constructable custom types, while seamlessly supporting the old API; see Tutorial.mp for details
This commit is contained in:
parent
5c05c687ef
commit
b46d569f8c
@ -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<Vec3>();
|
||||
node["end"] = Vec3(2, -1, 0);
|
||||
```
|
||||
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<T>::decode'
|
||||
method and introduces the abortion of the deserialization process by throwing
|
||||
an `DecodeException`.
|
||||
|
||||
```cpp
|
||||
namespace YAML {
|
||||
template <>
|
||||
struct convert<NonDefCtorVec3> {
|
||||
static Node encode(const NonDefCtorVec3& rhs) {
|
||||
return convert<Vec3>::encode(rhs);
|
||||
}
|
||||
|
||||
static NonDefCtorVec3 decode(const Node& node) {
|
||||
if (!node.IsSequence() || node.size() != 3) {
|
||||
throw YAML::conversion::DecodeException();
|
||||
}
|
||||
return {node[0].as<double>(), node[1].as<double>(), node[2].as<double>()};
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The behavior is exactly the same
|
||||
|
||||
```cpp
|
||||
YAML::Node node = YAML::Load("start: [1, 3, 0]");
|
||||
NonDefCtorVec3 v = node["start"].as<NonDefCtorVec3>();
|
||||
node["end"] = NonDefCtorVec3(2, -1, 0);
|
||||
```
|
||||
|
||||
@ -96,30 +96,84 @@ struct remove_idx<Key,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//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 <bool AorB>
|
||||
struct static_switch;
|
||||
|
||||
template<> //new api
|
||||
struct static_switch<true> {
|
||||
template<class T>
|
||||
static T call(const Node& node) {
|
||||
return convert<T>::decode(node);
|
||||
}
|
||||
};
|
||||
|
||||
template<> //old api
|
||||
struct static_switch<false> {
|
||||
template<class T>
|
||||
static T call(const Node& node) {
|
||||
T t;
|
||||
if (convert<T>::decode(node, t))
|
||||
return t;
|
||||
throw conversion::DecodeException();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
//detect the method of the new api
|
||||
template <typename>
|
||||
std::false_type has_decode_new_api(long);
|
||||
|
||||
template <typename T>
|
||||
auto has_decode_new_api(int)
|
||||
-> decltype( T::decode(std::declval<const Node&>()), std::true_type{});
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline bool node::equals(const T& rhs, shared_memory_holder pMemory) {
|
||||
|
||||
try {
|
||||
const auto rslt = convert<T>::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<decltype(has_decode_new_api<convert<T>>(
|
||||
0))::value>::template call<T>(Node(*this, pMemory)) == rhs;
|
||||
#else
|
||||
if constexpr (decltype(has_decode_new_api<convert<T>>(0))::value >)
|
||||
return convert<T>::decode(Node(*this, pMemory)) == rhs;
|
||||
else {
|
||||
T lhs;
|
||||
if (convert<T>::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<std::string>::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<std::string>::decode(Node(*this, std::move(pMemory)), lhs)) {
|
||||
return lhs == rhs;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// indexing
|
||||
template <typename Key>
|
||||
inline node* node_data::get(const Key& key,
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
#include "yaml-cpp/node/node.h"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace YAML {
|
||||
inline Node::Node()
|
||||
@ -88,6 +88,44 @@ inline NodeType::value Node::Type() const {
|
||||
|
||||
// access
|
||||
|
||||
//detect the method of the new api
|
||||
template <typename>
|
||||
std::false_type has_decode_new_api(long);
|
||||
|
||||
template <typename T>
|
||||
auto has_decode_new_api(int)
|
||||
-> decltype( T::decode(std::declval<const Node&>()), 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 <bool AorB>
|
||||
struct static_switch;
|
||||
|
||||
template<> //new api
|
||||
struct static_switch<true> {
|
||||
template<class T>
|
||||
static T call(const Node& node) {
|
||||
return convert<T>::decode(node);
|
||||
}
|
||||
};
|
||||
|
||||
template<> //old api
|
||||
struct static_switch<false> {
|
||||
template<class T>
|
||||
static T call(const Node& node) {
|
||||
T t;
|
||||
if (convert<T>::decode(node, t))
|
||||
return t;
|
||||
throw conversion::DecodeException();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
// template helpers
|
||||
template <typename T, typename S>
|
||||
struct as_if {
|
||||
@ -99,13 +137,25 @@ struct as_if {
|
||||
return fallback;
|
||||
|
||||
try {
|
||||
return convert<T>::decode(node);
|
||||
#ifdef PRE_CPP17_SHIM
|
||||
return static_switch<decltype(has_decode_new_api<convert<T>>(
|
||||
0))::value>::template call<T>(node);
|
||||
#else
|
||||
if constexpr (decltype(has_decodex<convert<T>>(0))::value >)
|
||||
return convert<T>::decodex(node);
|
||||
else {
|
||||
T t;
|
||||
if (convert<T>::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<Node, S> {
|
||||
} catch (const conversion::DecodeException& e) {
|
||||
return fallback;
|
||||
} catch (...) {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -154,14 +204,27 @@ struct as_if<T, void> {
|
||||
throw TypedBadConversion<T>(node.Mark());
|
||||
|
||||
try {
|
||||
return convert<T>::decode(node);
|
||||
#ifdef PRE_CPP17_SHIM
|
||||
return static_switch<decltype(has_decode_new_api<convert<T>>(
|
||||
0))::value>::template call<T>(node);
|
||||
#else
|
||||
if constexpr (decltype(has_decodex<convert<T>>(0))::value >)
|
||||
return convert<T>::decodex(node);
|
||||
else {
|
||||
T t;
|
||||
if (convert<T>::decode(node, t))
|
||||
return t;
|
||||
throw conversion::DecodeException();
|
||||
}
|
||||
#endif
|
||||
} catch(const conversion::DecodeException& e) {
|
||||
throw TypedBadConversion<T>(node.Mark());
|
||||
throw TypedBadConversion<T>(node.Mark());
|
||||
} catch (...) {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
throw;
|
||||
}
|
||||
};
|
||||
};
|
||||
#undef PRE_CPP17_SHIM
|
||||
|
||||
template <>
|
||||
struct as_if<std::string, void> {
|
||||
|
||||
@ -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 <typename T>
|
||||
struct convert;
|
||||
}
|
||||
|
||||
@ -42,6 +42,30 @@ 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>>>;
|
||||
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>>>;
|
||||
|
||||
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<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;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct convert<NonDefCtorVec3> {
|
||||
static Node encode(const NonDefCtorVec3& rhs) {
|
||||
return convert<Vec3>::encode(rhs);
|
||||
}
|
||||
|
||||
static NonDefCtorVec3 decode(const Node& node) {
|
||||
if (!node.IsSequence() || node.size() != 3) {
|
||||
throw YAML::conversion::DecodeException();
|
||||
}
|
||||
return {node[0].as<double>(), node[1].as<double>(), node[2].as<double>()};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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>(), (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>(), (NonDefCtorVec3{1.0, 2.0, 3.0}));
|
||||
}
|
||||
|
||||
class NodeEmitterTest : public ::testing::Test {
|
||||
protected:
|
||||
void ExpectOutput(const std::string& output, const Node& node) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user