将 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,它可以包含以下类型之一: floatstd::stringboolstd::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)functionvalues 源自一个方法获取 char* 的方法和´char** argv+ int argcfor values. Theexecutemethod now is supposed to call the correctmethod` 实例函数

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_callcall_generic.

valueclass有相应的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;
};

(我假设这个答案是 。)

从那里开始,我们需要我们的元类型和一些特征来将它们分支出来。我使用部分专业化来实现它们,但也有其他方法。

// 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...>{});
    }
};

我还将创建一个辅助函数来制作 methods:

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

Live example.

它并不完美,但这应该能让您大致了解如何操作。

将您的方法更改为:

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