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:
marcel 2022-03-03 11:03:33 +01:00
parent 5c05c687ef
commit b46d569f8c
5 changed files with 268 additions and 24 deletions

View File

@ -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);
```

View File

@ -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,

View File

@ -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> {

View File

@ -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;
}

View File

@ -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) {