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
|
```cpp
|
||||||
YAML::Node node = YAML::Load("start: [1, 3, 0]");
|
YAML::Node node = YAML::Load("start: [1, 3, 0]");
|
||||||
Vec3 v = node["start"].as<Vec3>();
|
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>
|
template <typename T>
|
||||||
inline bool node::equals(const T& rhs, shared_memory_holder pMemory) {
|
inline bool node::equals(const T& rhs, shared_memory_holder pMemory) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auto rslt = convert<T>::decode(Node(*this, pMemory));
|
#ifdef PRE_CPP17_SHIM
|
||||||
return rslt == rhs;
|
return static_switch<decltype(has_decode_new_api<convert<T>>(
|
||||||
} catch (const conversion::DecodeException& e) {
|
0))::value>::template call<T>(Node(*this, pMemory)) == rhs;
|
||||||
return false;
|
#else
|
||||||
} catch(...) {
|
if constexpr (decltype(has_decode_new_api<convert<T>>(0))::value >)
|
||||||
std::rethrow_exception(std::current_exception());
|
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) {
|
inline bool node::equals(const char* rhs, shared_memory_holder pMemory) {
|
||||||
try {
|
std::string lhs;
|
||||||
const auto rslt =
|
if (convert<std::string>::decode(Node(*this, std::move(pMemory)), lhs)) {
|
||||||
convert<std::string>::decode(Node(*this, std::move(pMemory)));
|
return lhs == rhs;
|
||||||
return rslt == rhs;
|
|
||||||
} catch (const conversion::DecodeException& e) {
|
|
||||||
return false;
|
|
||||||
} catch(...) {
|
|
||||||
std::rethrow_exception(std::current_exception());
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// indexing
|
// indexing
|
||||||
template <typename Key>
|
template <typename Key>
|
||||||
inline node* node_data::get(const Key& key,
|
inline node* node_data::get(const Key& key,
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
#include "yaml-cpp/node/node.h"
|
#include "yaml-cpp/node/node.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
namespace YAML {
|
namespace YAML {
|
||||||
inline Node::Node()
|
inline Node::Node()
|
||||||
@ -88,6 +88,44 @@ inline NodeType::value Node::Type() const {
|
|||||||
|
|
||||||
// access
|
// 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 helpers
|
||||||
template <typename T, typename S>
|
template <typename T, typename S>
|
||||||
struct as_if {
|
struct as_if {
|
||||||
@ -99,13 +137,25 @@ struct as_if {
|
|||||||
return fallback;
|
return fallback;
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (const conversion::DecodeException& e) {
|
||||||
return fallback;
|
return fallback;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::rethrow_exception(std::current_exception());
|
throw;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
//specialize for Node
|
//specialize for Node
|
||||||
@ -125,7 +175,7 @@ struct as_if<Node, S> {
|
|||||||
} catch (const conversion::DecodeException& e) {
|
} catch (const conversion::DecodeException& e) {
|
||||||
return fallback;
|
return fallback;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::rethrow_exception(std::current_exception());
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -154,14 +204,27 @@ struct as_if<T, void> {
|
|||||||
throw TypedBadConversion<T>(node.Mark());
|
throw TypedBadConversion<T>(node.Mark());
|
||||||
|
|
||||||
try {
|
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) {
|
} catch(const conversion::DecodeException& e) {
|
||||||
throw TypedBadConversion<T>(node.Mark());
|
throw TypedBadConversion<T>(node.Mark());
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::rethrow_exception(std::current_exception());
|
throw;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
#undef PRE_CPP17_SHIM
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct as_if<std::string, void> {
|
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);
|
YAML_CPP_API Node Clone(const Node& node);
|
||||||
|
|
||||||
|
//forward declare this customization point for all types
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct convert;
|
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 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>>>;
|
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
|
} // anonymous namespace
|
||||||
|
|
||||||
using ::testing::AnyOf;
|
using ::testing::AnyOf;
|
||||||
@ -56,6 +80,45 @@ using ::testing::Eq;
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace YAML {
|
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 {
|
namespace {
|
||||||
TEST(NodeTest, SimpleScalar) {
|
TEST(NodeTest, SimpleScalar) {
|
||||||
Node node = Node("Hello, World!");
|
Node node = Node("Hello, World!");
|
||||||
@ -674,6 +737,24 @@ TEST(NodeTest, AccessNonexistentKeyOnConstNode) {
|
|||||||
ASSERT_FALSE(other["5"]);
|
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 {
|
class NodeEmitterTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
void ExpectOutput(const std::string& output, const Node& node) {
|
void ExpectOutput(const std::string& output, const Node& node) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user