将 std::variant 的包装器 std::vector 传递给可变参数 class,在不知道具体细节的情况下包装可变参数方法
Passing std::vector of wrapper of std::variant into variadic class, wrapping variadic method without knowing the specifics
目标
我尝试创建一组 classes,删除用于在 C++ 中实现游戏扩展的样板代码。
为此,我指定了一个 value
class,它可以包含以下类型之一:
float
、std::string
、bool
、std::vector<value>
、void
为此,我想要一个 host
class,我可以向其中添加一个或多个 method
实例,如下所示:
using namespace std::string_literals;
host h;
h.add(
method<bool, req<std::string>, req<std::string>, opt<bool>>("compare_strings"s,
[](std::string s_orig, std::string s_comp, std::optional<bool> ingore_case) -> bool {
if (ignore_case.has_value() && ignore_case.value()) {
// ... lowercase both
}
return s_orig.compare(s_comp) == 0;
}));
请注意,req<T>
应该是需要给定值的元信息,opt<T>
是不需要给定值的元信息,只能在所有必需参数之后提供。
host
class 现在包含一个方法 execute(std::string function, std::vector<value> values)
和 function
和 values
源自一个方法获取 char*
的方法和´char** argv+
int argcfor values. The
executemethod now is supposed to call the correct
method` 实例函数
value host::execute(std::string function, std::vector<value> values) {
// get matching method group
std::vector<method> mthds = m_methods[function];
// get matching parameter list
for (method& mthd : mthds) {
if (mthd.can_call(mthds, values)) {
// call generic method
auto res = mthd.call_generic(values);
// pass result back to callee
// return [...]
}
}
// return error back to callee
// return [...]
}
这意味着实际的 method
class 现在需要正确地处理两个方法 can_call
和 call_generic
.
value
class有相应的template<typename T> bool is()
和template<typename T> T get()
方法。
剩下的
我确实有过其他尝试,但由于那些失败了,我删除了它们(在后面不是很聪明,但需要把整个事情弄清楚,因为另一个人依赖于结果工作)现在无法弄清楚之前的另一次尝试......所以这就是我现在剩下的:
class method_base
{
public:
template<typename T> struct in { using type = T; };
template<typename T> struct opt { using type = T; };
public:
virtual bool can_call(std::vector<sqf::value> values) = 0;
virtual sqf::value call_generic(std::vector<sqf::value> values) = 0;
};
template<typename T, typename ... TArgs>
class method : public method_base
{
func m_func;
sqf::value val
public:
using func = T(*)(TArgs...);
method(func f) : m_func(f) {}
virtual retval can_call(std::vector<sqf::value> values) override
{
}
};
附录
如果有什么不清楚、令人困惑或者您还有其他问题,请尽管问他们。我会尽力重新表述任何不清楚的地方,因为这将极大地帮助将来开发进一步的扩展,可能会为如何在社区中为相关游戏创建扩展定义一个“转到”方式(Arma 3 以防万一有人想知道)
我可能会注意到,这几乎是我第一次深入研究元编程,所以我提出的东西可能根本不可能实现。如果是这样,我想问你是否也可以解释为什么会这样,我尝试的事情是不可能的。
解决方案
再次感谢所有回答这个问题的人。我最终在这里结合了所有解决方案的大部分内容,并且在这个过程中学到了很多东西。我最终得到的最终实现如下所示:
namespace meta
{
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
template <typename ArgType>
struct def_value { static ArgType value() { return {}; } };
template <typename ArgType>
struct get_type { using type = ArgType; };
template <typename ArgType>
struct get_type<std::optional<ArgType>> { using type = ArgType; };
}
struct method {
std::function<bool(const std::vector<value>&)> m_can_call;
std::function<value(const std::vector<value>&)> m_call;
template <typename ... Args, std::size_t... IndexSequence>
static bool can_call_impl(const std::vector<value>& values, std::index_sequence<IndexSequence...> s) {
// values max args
return values.size() <= sizeof...(Args) &&
// for every Arg, either...
(... && (
// the value provides that argument and its the correct type, or...
(IndexSequence < values.size() && sqf::is<sqf::meta::get_type<Args>::type>(values[IndexSequence])) ||
// the value does not provide that argument and the arg is an optional
(IndexSequence >= values.size() && sqf::meta::is_optional_v<Args>)
));
}
template <typename Ret, typename ... Args, std::size_t... IndexSequence>
static value call_impl(std::function<Ret(Args...)> f, const std::vector<value>& values, std::index_sequence<IndexSequence...>) {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(f,
(IndexSequence < values.size() ? sqf::get<sqf::meta::get_type<Args>::type>(values[IndexSequence])
: sqf::meta::def_value<Args>::value())...)
};
}
public:
template <typename Ret, typename ... Args>
method(std::function<Ret(Args...)> f) :
m_can_call([](const std::vector<value>& values) -> bool
{
return can_call_impl<Args...>(values, std::index_sequence_for<Args...>{});
}),
m_call([f](const std::vector<value>& values) -> value
{
return call_impl<Ret, Args...>(f, values, std::index_sequence_for<Args...>{});
})
{
}
bool can_call(const std::vector<value>& values) const { return m_can_call(values); }
value call_generic(const std::vector<value>& values) const { return m_call(values); }
// to handle lambda
template <typename F>
method static create(F f) { return method{ std::function{f} }; }
};
假设一种检查当前值类型的方法 (template <typename T> bool value::isA<T>()
) 和一种检索值的方法 (template <typename T> /*const*/T& get(/*const*/ value&)
)
看来你可以:
struct method
{
template <typename Ret, typename ... Ts>
method(std::function<Ret(Ts...)> f) : method(std::index_sequence<sizeof...(Ts)>(), f)
{}
template <typename Ret, typename ... Ts, std::size_t ... Is>
method(std::index_sequence<Is...>, std::function<Ret(Ts...)> f) :
isOk([](const std::vector<value>& values) {
return ((values.size() == sizeof...(Is)) && ... && values[Is].isA<Ts>());
}),
call([f](const std::vector<value>& values){
return f(get<Ts>(values[Is])...);
})
{}
// to handle lambda
template <typename F>
static fromCallable(F f) { return method{std::function{f}}; }
std::function<bool(const std::vector<value>&)> isOk;
std::function<value(const std::vector<value>&)> call;
};
这是一个简单示例,其中包括 ret<T>
和 opt<T>
的机制。您没有提供任何关于 value
是什么的信息,所以我将假设如下:
struct value {
// using `std::monostate` instead of `void`
std::variant<float, std::string, bool, std::vector<value>, std::monostate> data;
};
(我假设这个答案是 c++17。)
从那里开始,我们需要我们的元类型和一些特征来将它们分支出来。我使用部分专业化来实现它们,但也有其他方法。
// types to determine optional vs. required
template <typename T>
struct req { using type = T; };
template <typename T>
struct opt { using type = T; };
// trait to determine if it's an optional type
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<opt<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
// get the "real" function parameter type
template <typename ArgType>
struct real_type;
template <typename ArgType>
using real_type_t = typename real_type<ArgType>::type;
template <typename T>
struct real_type<req<T>> { using type = T; };
template <typename T>
struct real_type<opt<T>> { using type = std::optional<T>; };
现在我们实施[=17=]。我将使用与 method_base
类似的多态关系,就像您在部分演示中所做的那样;我还在传入的函数类型上模板,以允许例如使用对类型的 const 引用而不是类型本身的函数。
实现本身使用了使用 std::index_sequence
委托辅助函数的常见技巧,并折叠表达式以“迭代”可变参数模板参数。
// base class for polymorphism
struct method_base {
virtual ~method_base() = default;
virtual bool can_call(const std::vector<value>& values) const = 0;
virtual value call_generic(const std::vector<value>& values) const = 0;
};
// provide a different method implementation for each set of args
// I also overload on
template<typename RetType, typename Fn, typename... Args>
struct method : method_base {
private:
Fn func;
static_assert(std::is_invocable_r_v<RetType, Fn, real_type_t<Args>...>,
"function must be callable with given args");
public:
// accept any function that looks sort of like what we expect;
// static assert above makes sure it's sensible
template <typename G>
method(G&& func) : func(std::forward<G>(func)) {}
template <std::size_t... Is>
bool can_call_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
// for every Arg, either...
return (... and (
// the value provides that argument and its the correct type, or...
(Is < values.size() and std::holds_alternative<typename Args::type>(values[Is].data))
// the value does not provide that argument and the arg is an optional
or (Is >= values.size() and is_optional_v<Args>)
));
}
bool can_call(const std::vector<value>& values) const override {
return can_call_impl(values, std::index_sequence_for<Args...>{});
}
template <std::size_t... Is>
value call_generic_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(func,
(Is < values.size() ? std::get<typename Args::type>(values[Is].data)
: real_type_t<Args>{})...)
};
}
value call_generic(const std::vector<value>& values) const override {
return call_generic_impl(values, std::index_sequence_for<Args...>{});
}
};
我还将创建一个辅助函数来制作 method
s:
template <typename RetType, typename... Args, typename Fn>
std::unique_ptr<method_base> make_method(Fn&& func) {
return std::make_unique<method<RetType, std::decay_t<Fn>, Args...>>(std::forward<Fn>(func));
}
它并不完美,但这应该能让您大致了解如何操作。
将您的方法更改为:
method< R(Args...) >
你的标签似乎没用。检测可选... std::optional
.
对于存储,使用标准变体。为 void 使用一些非 void 类型(我不在乎什么)。
作为第一步,我们的目标是完美兼容。
template<class...Args>
struct check_signature {
bool operator()( std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
std::size_t i=0;
return (std::holds_alternative<Args>(values[i++])&&...);
}
};
这可以存储在 std::function<bool(std::span<value const>)>
中或仅在您的 class 实现中调用。
类似的代码可以存储调用。
template<class F, class R, class...Args>
struct execute {
F f;
template<std::size_t...Is>
R operator()( std::index_sequence<Is...>, std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
return f( std::get<Args>(values[Is])... );
}
R operator()( std::span<value const> values ) const {
return (*this)( std::make_index_sequence<sizeof...(Args)>{}, values );
}
};
可能需要为假货做一些工作 void
。
你的方法现在是一个聚合。
struct method {
std::function<bool(std::span<value const>)> can_call;
std::function<value(std::span<value const>)> execute;
};
如果你愿意的话。上面的两个模板对象可以存储在这两个std函数中。
可能有tpyos,我只是写在我的phone上,还没有测试。
将其扩展到涵盖可选参数需要做一些工作。但没什么难的。
在这两种情况下,您都将编写一个辅助函数来说明参数是否兼容,或者根据您是否超过传入向量的末尾来生成值。
即,std::get<Args>(values[Is])...
变为 getArgFrom<Is, Args>{}(values)...
,如果 Is>values.size()
.
,我们专门针对 std optional 生成 nullopt
目标
我尝试创建一组 classes,删除用于在 C++ 中实现游戏扩展的样板代码。
为此,我指定了一个 value
class,它可以包含以下类型之一:
float
、std::string
、bool
、std::vector<value>
、void
为此,我想要一个 host
class,我可以向其中添加一个或多个 method
实例,如下所示:
using namespace std::string_literals;
host h;
h.add(
method<bool, req<std::string>, req<std::string>, opt<bool>>("compare_strings"s,
[](std::string s_orig, std::string s_comp, std::optional<bool> ingore_case) -> bool {
if (ignore_case.has_value() && ignore_case.value()) {
// ... lowercase both
}
return s_orig.compare(s_comp) == 0;
}));
请注意,req<T>
应该是需要给定值的元信息,opt<T>
是不需要给定值的元信息,只能在所有必需参数之后提供。
host
class 现在包含一个方法 execute(std::string function, std::vector<value> values)
和 function
和 values
源自一个方法获取 char*
的方法和´char** argv+
int argcfor values. The
executemethod now is supposed to call the correct
method` 实例函数
value host::execute(std::string function, std::vector<value> values) {
// get matching method group
std::vector<method> mthds = m_methods[function];
// get matching parameter list
for (method& mthd : mthds) {
if (mthd.can_call(mthds, values)) {
// call generic method
auto res = mthd.call_generic(values);
// pass result back to callee
// return [...]
}
}
// return error back to callee
// return [...]
}
这意味着实际的 method
class 现在需要正确地处理两个方法 can_call
和 call_generic
.
value
class有相应的template<typename T> bool is()
和template<typename T> T get()
方法。
剩下的
我确实有过其他尝试,但由于那些失败了,我删除了它们(在后面不是很聪明,但需要把整个事情弄清楚,因为另一个人依赖于结果工作)现在无法弄清楚之前的另一次尝试......所以这就是我现在剩下的:
class method_base
{
public:
template<typename T> struct in { using type = T; };
template<typename T> struct opt { using type = T; };
public:
virtual bool can_call(std::vector<sqf::value> values) = 0;
virtual sqf::value call_generic(std::vector<sqf::value> values) = 0;
};
template<typename T, typename ... TArgs>
class method : public method_base
{
func m_func;
sqf::value val
public:
using func = T(*)(TArgs...);
method(func f) : m_func(f) {}
virtual retval can_call(std::vector<sqf::value> values) override
{
}
};
附录
如果有什么不清楚、令人困惑或者您还有其他问题,请尽管问他们。我会尽力重新表述任何不清楚的地方,因为这将极大地帮助将来开发进一步的扩展,可能会为如何在社区中为相关游戏创建扩展定义一个“转到”方式(Arma 3 以防万一有人想知道)
我可能会注意到,这几乎是我第一次深入研究元编程,所以我提出的东西可能根本不可能实现。如果是这样,我想问你是否也可以解释为什么会这样,我尝试的事情是不可能的。
解决方案
再次感谢所有回答这个问题的人。我最终在这里结合了所有解决方案的大部分内容,并且在这个过程中学到了很多东西。我最终得到的最终实现如下所示:
namespace meta
{
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
template <typename ArgType>
struct def_value { static ArgType value() { return {}; } };
template <typename ArgType>
struct get_type { using type = ArgType; };
template <typename ArgType>
struct get_type<std::optional<ArgType>> { using type = ArgType; };
}
struct method {
std::function<bool(const std::vector<value>&)> m_can_call;
std::function<value(const std::vector<value>&)> m_call;
template <typename ... Args, std::size_t... IndexSequence>
static bool can_call_impl(const std::vector<value>& values, std::index_sequence<IndexSequence...> s) {
// values max args
return values.size() <= sizeof...(Args) &&
// for every Arg, either...
(... && (
// the value provides that argument and its the correct type, or...
(IndexSequence < values.size() && sqf::is<sqf::meta::get_type<Args>::type>(values[IndexSequence])) ||
// the value does not provide that argument and the arg is an optional
(IndexSequence >= values.size() && sqf::meta::is_optional_v<Args>)
));
}
template <typename Ret, typename ... Args, std::size_t... IndexSequence>
static value call_impl(std::function<Ret(Args...)> f, const std::vector<value>& values, std::index_sequence<IndexSequence...>) {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(f,
(IndexSequence < values.size() ? sqf::get<sqf::meta::get_type<Args>::type>(values[IndexSequence])
: sqf::meta::def_value<Args>::value())...)
};
}
public:
template <typename Ret, typename ... Args>
method(std::function<Ret(Args...)> f) :
m_can_call([](const std::vector<value>& values) -> bool
{
return can_call_impl<Args...>(values, std::index_sequence_for<Args...>{});
}),
m_call([f](const std::vector<value>& values) -> value
{
return call_impl<Ret, Args...>(f, values, std::index_sequence_for<Args...>{});
})
{
}
bool can_call(const std::vector<value>& values) const { return m_can_call(values); }
value call_generic(const std::vector<value>& values) const { return m_call(values); }
// to handle lambda
template <typename F>
method static create(F f) { return method{ std::function{f} }; }
};
假设一种检查当前值类型的方法 (template <typename T> bool value::isA<T>()
) 和一种检索值的方法 (template <typename T> /*const*/T& get(/*const*/ value&)
)
看来你可以:
struct method
{
template <typename Ret, typename ... Ts>
method(std::function<Ret(Ts...)> f) : method(std::index_sequence<sizeof...(Ts)>(), f)
{}
template <typename Ret, typename ... Ts, std::size_t ... Is>
method(std::index_sequence<Is...>, std::function<Ret(Ts...)> f) :
isOk([](const std::vector<value>& values) {
return ((values.size() == sizeof...(Is)) && ... && values[Is].isA<Ts>());
}),
call([f](const std::vector<value>& values){
return f(get<Ts>(values[Is])...);
})
{}
// to handle lambda
template <typename F>
static fromCallable(F f) { return method{std::function{f}}; }
std::function<bool(const std::vector<value>&)> isOk;
std::function<value(const std::vector<value>&)> call;
};
这是一个简单示例,其中包括 ret<T>
和 opt<T>
的机制。您没有提供任何关于 value
是什么的信息,所以我将假设如下:
struct value {
// using `std::monostate` instead of `void`
std::variant<float, std::string, bool, std::vector<value>, std::monostate> data;
};
(我假设这个答案是 c++17。)
从那里开始,我们需要我们的元类型和一些特征来将它们分支出来。我使用部分专业化来实现它们,但也有其他方法。
// types to determine optional vs. required
template <typename T>
struct req { using type = T; };
template <typename T>
struct opt { using type = T; };
// trait to determine if it's an optional type
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<opt<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
// get the "real" function parameter type
template <typename ArgType>
struct real_type;
template <typename ArgType>
using real_type_t = typename real_type<ArgType>::type;
template <typename T>
struct real_type<req<T>> { using type = T; };
template <typename T>
struct real_type<opt<T>> { using type = std::optional<T>; };
现在我们实施[=17=]。我将使用与 method_base
类似的多态关系,就像您在部分演示中所做的那样;我还在传入的函数类型上模板,以允许例如使用对类型的 const 引用而不是类型本身的函数。
实现本身使用了使用 std::index_sequence
委托辅助函数的常见技巧,并折叠表达式以“迭代”可变参数模板参数。
// base class for polymorphism
struct method_base {
virtual ~method_base() = default;
virtual bool can_call(const std::vector<value>& values) const = 0;
virtual value call_generic(const std::vector<value>& values) const = 0;
};
// provide a different method implementation for each set of args
// I also overload on
template<typename RetType, typename Fn, typename... Args>
struct method : method_base {
private:
Fn func;
static_assert(std::is_invocable_r_v<RetType, Fn, real_type_t<Args>...>,
"function must be callable with given args");
public:
// accept any function that looks sort of like what we expect;
// static assert above makes sure it's sensible
template <typename G>
method(G&& func) : func(std::forward<G>(func)) {}
template <std::size_t... Is>
bool can_call_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
// for every Arg, either...
return (... and (
// the value provides that argument and its the correct type, or...
(Is < values.size() and std::holds_alternative<typename Args::type>(values[Is].data))
// the value does not provide that argument and the arg is an optional
or (Is >= values.size() and is_optional_v<Args>)
));
}
bool can_call(const std::vector<value>& values) const override {
return can_call_impl(values, std::index_sequence_for<Args...>{});
}
template <std::size_t... Is>
value call_generic_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(func,
(Is < values.size() ? std::get<typename Args::type>(values[Is].data)
: real_type_t<Args>{})...)
};
}
value call_generic(const std::vector<value>& values) const override {
return call_generic_impl(values, std::index_sequence_for<Args...>{});
}
};
我还将创建一个辅助函数来制作 method
s:
template <typename RetType, typename... Args, typename Fn>
std::unique_ptr<method_base> make_method(Fn&& func) {
return std::make_unique<method<RetType, std::decay_t<Fn>, Args...>>(std::forward<Fn>(func));
}
它并不完美,但这应该能让您大致了解如何操作。
将您的方法更改为:
method< R(Args...) >
你的标签似乎没用。检测可选... std::optional
.
对于存储,使用标准变体。为 void 使用一些非 void 类型(我不在乎什么)。
作为第一步,我们的目标是完美兼容。
template<class...Args>
struct check_signature {
bool operator()( std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
std::size_t i=0;
return (std::holds_alternative<Args>(values[i++])&&...);
}
};
这可以存储在 std::function<bool(std::span<value const>)>
中或仅在您的 class 实现中调用。
类似的代码可以存储调用。
template<class F, class R, class...Args>
struct execute {
F f;
template<std::size_t...Is>
R operator()( std::index_sequence<Is...>, std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
return f( std::get<Args>(values[Is])... );
}
R operator()( std::span<value const> values ) const {
return (*this)( std::make_index_sequence<sizeof...(Args)>{}, values );
}
};
可能需要为假货做一些工作 void
。
你的方法现在是一个聚合。
struct method {
std::function<bool(std::span<value const>)> can_call;
std::function<value(std::span<value const>)> execute;
};
如果你愿意的话。上面的两个模板对象可以存储在这两个std函数中。
可能有tpyos,我只是写在我的phone上,还没有测试。
将其扩展到涵盖可选参数需要做一些工作。但没什么难的。
在这两种情况下,您都将编写一个辅助函数来说明参数是否兼容,或者根据您是否超过传入向量的末尾来生成值。
即,std::get<Args>(values[Is])...
变为 getArgFrom<Is, Args>{}(values)...
,如果 Is>values.size()
.