【问题标题】:Passing std::vector of wrapper of std::variant into variadic class, wrapping variadic method without knowing the specifics将 std::variant 的包装器的 std::vector 传递给可变参数类,在不知道细节的情况下包装可变参数方法
【发布时间】:2021-01-07 00:40:23
【问题描述】:

目标

我尝试创建一组类来删除样板代码,以便在 C++ 中实现对游戏的扩展。 为此,我有一个指定的 value 类,它可以包含以下类型之一: floatstd::stringboolstd::vector<value>void

为此,我想要一个 host 类,我可以向其中添加一个或多个 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&lt;T&gt; 应该是需要给定值的元信息,opt&lt;T&gt; 应该是不需要给定值并且只能在所有必需参数之后提供的元信息。

host 类现在包含一个方法execute(std::string function, std::vector&lt;value&gt; values),其中functionvalues 源自一个方法,该方法为方法获取char* 和'char** argv+ int argcfor values. Theexecute@987654339 @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 类现在需要正确处理can_callcall_generic 两个方法。

value 类有对应的template&lt;typename T&gt; bool is()template&lt;typename T&gt; 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} }; }
};

【问题讨论】:

    标签: c++ templates variadic-templates template-meta-programming


    【解决方案1】:

    假设一种检查当前值类型的方法 (template &lt;typename T&gt; bool value::isA&lt;T&gt;()) 和一种检索值的方法 (template &lt;typename T&gt; /*const*/T&amp; get(/*const*/ value&amp;))

    看来你可以这样做:

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

    【讨论】:

    • 介意解释一下这里到底发生了什么?试图了解这种方法在每一步中如何准确地工作,以便能够在未来扩展功能集。提前致谢。
    • 我从函数中创建了 2 个std::function。使用索引序列技巧来获得向量的索引。 optional 应在 isAget 中处理。
    • 想了更多关于这样的语法糖:return (values.size() == sizeof...(Is) &amp;&amp; ... &amp;&amp; values[Is].isA&lt;Ts&gt;());,因为到目前为止这对我来说仍然是一个谜。例如,&amp;&amp; ... &amp;&amp; 发生了什么?
    • 非常感谢,不知道命名方案是怎样的。在实施后,明天将勾选或添加与此答案相关的更多问题的新评论:)
    【解决方案2】:

    这是一个简单的示例,包括 ret&lt;T&gt;opt&lt;T&gt; 的机制。你还没有提供任何关于 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>; };
    

    现在我们实现method。我将使用与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.

    它并不完美,但这应该让您大致了解如何做到这一点。

    【讨论】:

    • 我也非常感谢您的回答,明天会好好检查一下,因为它很多而且我已经准备好睡觉了。事实上,价值与解析等相关的一些额外方法差不多,这就是为什么我没有进一步指定它的原因。
    【解决方案3】:

    将您的方法更改为:

    method< R(Args...) >
    

    您的标签似乎没用。使用 ...std::optional 检测可选。

    对于存储,请使用 std 变体。为 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&lt;bool(std::span&lt;value const&gt;)&gt; 中,或者只是在您的类实现中调用。

    类似的代码可以存储callable。

    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,我只是在手机上写的,没有测试过。

    扩展它以涵盖可选参数是一些工作。但没什么难的。

    在这两种情况下,您都将编写一个辅助函数来判断参数是否兼容,或者根据您是否超过传入向量的末尾来生成值。

    即,std::get&lt;Args&gt;(values[Is])... 变为 getArgFrom&lt;Is, Args&gt;{}(values)...,并且我们专门针对 std 可选,如果 Is&gt;values.size() 则生成 nullopt。

    【讨论】:

    • 标签目前还没有什么用,这有点正确。但是,它们主要用于临时使用目的。将来,我想扩展他们的能力,这就是为什么他们的加入很重要:) 我也感谢您的回答。今天下班后深入检查。
    • “可能有tpyos”你喜欢那个^_^。
    • @jarod42 这是 C++ 的传统;阅读 C++ 标准草案的早期部分。
    猜你喜欢
    • 2017-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-16
    • 1970-01-01
    • 2012-09-27
    • 1970-01-01
    相关资源
    最近更新 更多