模板转换运算符和 boost::any 或 std::any
Template cast operator and boost::any or std::any
我写了一个通用的 class 来为任何 class 提供一个简单的基于 JSON 的初始化。
在我想将它应用于包含枚举的 class 之前,它就像一个魅力一样工作。
我的基础class首先解析JSON,找到JSON的子对象与派生的class具有相同的name
(我使用 CRTP ...),并构建一个 std::map<std::string, boost::any> _settings;
,其中键是字段名称,boost::any 包含字符串、整数、双精度和偶数数组。
我的派生 class 只需要实现一个更新函数,如下例所示:
class testJsonUpdate : public JsonSettingsCustomizer<testJsonUpdate>
{
public:
static const std::string class_name;
testJsonUpdate(const std::string& settings) {
_test_int = 0;
_test_vector = { 0.0f };
ParseAndUpdate(settings);
}
~testJsonUpdate() {}
int Update()
{
UPDATE_VALUE(testJsonUpdate, _test_int);
UPDATE_VALUE(testJsonUpdate, _test_vector);
return 0;
}
public:
uint32_t _test_int;
std::vector<float> _test_vector;
};
const std::string testJsonUpdate::class_name = "testJsonUpdate";
UPDATE_VALUE(testJsonUpdate, _test_int);
是一个宏,它的扩展使用下面的代码。
问题是当我的派生 class 有一个 emun 成员时我能做什么。 在那种情况下,boost::any 值是一个整数,现在调用 explicit operator T()
with T = "MYCLASS::enum type" 会触发异常,因为 boost::any_cast 从 int 到我的枚举类型 !
有没有一种方法可以编写 boost::any cast 用来解决该问题的 cast 运算符?
template<typename CALLER>
struct Value
{
std::string _key;
boost::any _value;
template<typename T>
explicit operator T() const
{
try
{
return boost::any_cast<T>(_value); // <== where it trigger exception
}
catch (...)
{
throw std::logic_error(CALLER::class_name + ": config string parsing issue");
}
}
template<typename T>
static Value<T> RetreiveValue(const std::map<std::string, boost::any> & settings, const std::string & key)
{
return{ key, settings.find(key)->second };
} // RetreiveValue
#define UPDATE_VALUE(caller, member) \
key = BUILD_KEY(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
member = (decltype(member)) RetreiveValue<caller>(_settings, key); \
}
用于演示该问题的完整示例,已在 https://www.onlinegdb.com/online_c++_compiler 上使用 C++17 进行测试(以获得 std::any 支持)
#include <iostream>
using namespace std;
#include <string>
#include <map>
#include <vector>
#include <memory>
#include <any>
template<typename CALLER>
struct Value
{
std::string _key;
std::any _value;
template<typename T>
explicit operator T() const
{
try
{
return std::any_cast<T>(_value);
}
catch (...)
{
//throw std::logic_error(CALLER::class_name + ": config string parsing issue");
throw;
}
}
}; // Value
template<typename T>
static Value<T> RetreiveValue(const std::map<std::string, std::any> & settings, const std::string & key)
{
return{ key, settings.find(key)->second };
} // RetreiveValue
// ----------------------------------------------------------------------------
template <typename CALLER, typename T> const std::string buildPrefix(const T &elt)
{
throw std::logic_error(CALLER::class_name + ": config string parsing issue");
} // buildName
template <typename CALLER> const std::string buildPrefix(const bool &elt) { return "b"; }
template <typename CALLER> const std::string buildPrefix(const std::string &elt) { return "s"; }
template <typename CALLER> const std::string buildPrefix(const int &elt) { return "i"; }
#define ADD_M_PREFIX(member) m ## member
#define BUILD_KEY(caller, member) buildPrefix<caller>(member) + #member;
#define BUILD_KEY_WITH_M_PREFIX(caller, member) buildPrefix<caller>( ADD_M_PREFIX(member) ) + #member;
#define UPDATE_VALUE(caller, member) \
key = BUILD_KEY(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
member = (decltype(member)) RetreiveValue<caller>(_settings, key); \
}
#define UPDATE_VALUE_WITH_M_PREFIX(caller, member) \
key = BUILD_KEY_WITH_M_PREFIX(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
ADD_M_PREFIX(member) = (decltype(ADD_M_PREFIX(member))) RetreiveValue<caller>(_settings, key); \
}
template<typename T>
class JsonSettingsCustomizer {
public:
JsonSettingsCustomizer() : _label(T::class_name) { }
virtual ~JsonSettingsCustomizer() {}
int ParseAndUpdate(const std::string& settings) {
//JSON Parsing to map
//fake data to test
_settings["i_integer"] = (int)(1);
_settings["s_msg"] = (std::string)("hello world");
_settings["i_enum_value"] = (int)(1);
T& derived = static_cast<T&>(*this);
auto ret = derived.Update();
return ret;
}
protected:
std::map<std::string, std::any> _settings;
std::string key;
std::string _label;
};
/************************************************************************************************/
// END TOOLING
/************************************************************************************************/
typedef enum : int {
enum_one = 1,
enum_two = 2
} ENUM_TYPE;
//extention for the new ENUM_TYPE
template<typename CALLER> const std::string buildPrefix(const ENUM_TYPE& elt) { return "i"; }
class testJsonUpdate : public JsonSettingsCustomizer<testJsonUpdate>
{
public:
static const std::string class_name;
testJsonUpdate(const std::string& settings) {
ParseAndUpdate(settings);
}
~testJsonUpdate() {}
int Update()
{
UPDATE_VALUE(testJsonUpdate, _integer);
UPDATE_VALUE(testJsonUpdate, _msg);
//UPDATE_VALUE(testJsonUpdate, _enum_value); // uncomment to break on the bad cast exception
return 0;
}
public:
int _integer;
std::string _msg;
ENUM_TYPE _enum_value;
};
const std::string testJsonUpdate::class_name = "testJsonUpdate";
int main() {
// your code goes here
testJsonUpdate o(".... JSON .... ");
std::cout << o._integer << std::endl;
std::cout << o._msg << std::endl;
return 0;
}
我不熟悉 boost,所以我将使用标准的等效结构。第一个问题是:你真的想要 any
还是 variant<string, int, ...>
是更好的选择?
无论如何,如果您只想包装演员表,您可以轻松做到:
template <typename T>
T json_cast(std::any const& val)
{
if constexpr (std::is_enum_v<T>)
{
return static_cast<T>(std::any_cast<int>(val));
}
else
{
return std::any_cast<T>(val);
}
}
这要求 any
实际上持有一个 int
。您也可以尝试 std::underlying_type_t<T>
而不是 int
,但是枚举的底层类型有一些怪癖,因此如果 any
持有一个 int,则转换实际上可能会失败。
C++14 版本:
template <typename T>
T json_cast(std::any const& val)
{
using cast_t = typename std::conditional<std::is_enum<T>::value, int, T>::type;
return static_cast<T>(std::any_cast<cast_t>(val));
}
我写了一个通用的 class 来为任何 class 提供一个简单的基于 JSON 的初始化。 在我想将它应用于包含枚举的 class 之前,它就像一个魅力一样工作。
我的基础class首先解析JSON,找到JSON的子对象与派生的class具有相同的name
(我使用 CRTP ...),并构建一个 std::map<std::string, boost::any> _settings;
,其中键是字段名称,boost::any 包含字符串、整数、双精度和偶数数组。
我的派生 class 只需要实现一个更新函数,如下例所示:
class testJsonUpdate : public JsonSettingsCustomizer<testJsonUpdate>
{
public:
static const std::string class_name;
testJsonUpdate(const std::string& settings) {
_test_int = 0;
_test_vector = { 0.0f };
ParseAndUpdate(settings);
}
~testJsonUpdate() {}
int Update()
{
UPDATE_VALUE(testJsonUpdate, _test_int);
UPDATE_VALUE(testJsonUpdate, _test_vector);
return 0;
}
public:
uint32_t _test_int;
std::vector<float> _test_vector;
};
const std::string testJsonUpdate::class_name = "testJsonUpdate";
UPDATE_VALUE(testJsonUpdate, _test_int);
是一个宏,它的扩展使用下面的代码。
问题是当我的派生 class 有一个 emun 成员时我能做什么。 在那种情况下,boost::any 值是一个整数,现在调用 explicit operator T()
with T = "MYCLASS::enum type" 会触发异常,因为 boost::any_cast 从 int 到我的枚举类型 !
有没有一种方法可以编写 boost::any cast 用来解决该问题的 cast 运算符?
template<typename CALLER>
struct Value
{
std::string _key;
boost::any _value;
template<typename T>
explicit operator T() const
{
try
{
return boost::any_cast<T>(_value); // <== where it trigger exception
}
catch (...)
{
throw std::logic_error(CALLER::class_name + ": config string parsing issue");
}
}
template<typename T>
static Value<T> RetreiveValue(const std::map<std::string, boost::any> & settings, const std::string & key)
{
return{ key, settings.find(key)->second };
} // RetreiveValue
#define UPDATE_VALUE(caller, member) \
key = BUILD_KEY(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
member = (decltype(member)) RetreiveValue<caller>(_settings, key); \
}
用于演示该问题的完整示例,已在 https://www.onlinegdb.com/online_c++_compiler 上使用 C++17 进行测试(以获得 std::any 支持)
#include <iostream>
using namespace std;
#include <string>
#include <map>
#include <vector>
#include <memory>
#include <any>
template<typename CALLER>
struct Value
{
std::string _key;
std::any _value;
template<typename T>
explicit operator T() const
{
try
{
return std::any_cast<T>(_value);
}
catch (...)
{
//throw std::logic_error(CALLER::class_name + ": config string parsing issue");
throw;
}
}
}; // Value
template<typename T>
static Value<T> RetreiveValue(const std::map<std::string, std::any> & settings, const std::string & key)
{
return{ key, settings.find(key)->second };
} // RetreiveValue
// ----------------------------------------------------------------------------
template <typename CALLER, typename T> const std::string buildPrefix(const T &elt)
{
throw std::logic_error(CALLER::class_name + ": config string parsing issue");
} // buildName
template <typename CALLER> const std::string buildPrefix(const bool &elt) { return "b"; }
template <typename CALLER> const std::string buildPrefix(const std::string &elt) { return "s"; }
template <typename CALLER> const std::string buildPrefix(const int &elt) { return "i"; }
#define ADD_M_PREFIX(member) m ## member
#define BUILD_KEY(caller, member) buildPrefix<caller>(member) + #member;
#define BUILD_KEY_WITH_M_PREFIX(caller, member) buildPrefix<caller>( ADD_M_PREFIX(member) ) + #member;
#define UPDATE_VALUE(caller, member) \
key = BUILD_KEY(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
member = (decltype(member)) RetreiveValue<caller>(_settings, key); \
}
#define UPDATE_VALUE_WITH_M_PREFIX(caller, member) \
key = BUILD_KEY_WITH_M_PREFIX(caller, member); \
if (_settings.end() != _settings.find(key)) \
{ \
ADD_M_PREFIX(member) = (decltype(ADD_M_PREFIX(member))) RetreiveValue<caller>(_settings, key); \
}
template<typename T>
class JsonSettingsCustomizer {
public:
JsonSettingsCustomizer() : _label(T::class_name) { }
virtual ~JsonSettingsCustomizer() {}
int ParseAndUpdate(const std::string& settings) {
//JSON Parsing to map
//fake data to test
_settings["i_integer"] = (int)(1);
_settings["s_msg"] = (std::string)("hello world");
_settings["i_enum_value"] = (int)(1);
T& derived = static_cast<T&>(*this);
auto ret = derived.Update();
return ret;
}
protected:
std::map<std::string, std::any> _settings;
std::string key;
std::string _label;
};
/************************************************************************************************/
// END TOOLING
/************************************************************************************************/
typedef enum : int {
enum_one = 1,
enum_two = 2
} ENUM_TYPE;
//extention for the new ENUM_TYPE
template<typename CALLER> const std::string buildPrefix(const ENUM_TYPE& elt) { return "i"; }
class testJsonUpdate : public JsonSettingsCustomizer<testJsonUpdate>
{
public:
static const std::string class_name;
testJsonUpdate(const std::string& settings) {
ParseAndUpdate(settings);
}
~testJsonUpdate() {}
int Update()
{
UPDATE_VALUE(testJsonUpdate, _integer);
UPDATE_VALUE(testJsonUpdate, _msg);
//UPDATE_VALUE(testJsonUpdate, _enum_value); // uncomment to break on the bad cast exception
return 0;
}
public:
int _integer;
std::string _msg;
ENUM_TYPE _enum_value;
};
const std::string testJsonUpdate::class_name = "testJsonUpdate";
int main() {
// your code goes here
testJsonUpdate o(".... JSON .... ");
std::cout << o._integer << std::endl;
std::cout << o._msg << std::endl;
return 0;
}
我不熟悉 boost,所以我将使用标准的等效结构。第一个问题是:你真的想要 any
还是 variant<string, int, ...>
是更好的选择?
无论如何,如果您只想包装演员表,您可以轻松做到:
template <typename T>
T json_cast(std::any const& val)
{
if constexpr (std::is_enum_v<T>)
{
return static_cast<T>(std::any_cast<int>(val));
}
else
{
return std::any_cast<T>(val);
}
}
这要求 any
实际上持有一个 int
。您也可以尝试 std::underlying_type_t<T>
而不是 int
,但是枚举的底层类型有一些怪癖,因此如果 any
持有一个 int,则转换实际上可能会失败。
C++14 版本:
template <typename T>
T json_cast(std::any const& val)
{
using cast_t = typename std::conditional<std::is_enum<T>::value, int, T>::type;
return static_cast<T>(std::any_cast<cast_t>(val));
}