温馨提示:本文翻译自stackoverflow.com,查看原文请点击:templates - In C++, how can I create a heterogenous vector containing variafic templated objects?
c++ inheritance templates variadic-templates factory

templates - 在C ++中,如何创建包含可变模板对象的异构矢量?

发布于 2020-04-09 09:58:49

嗨,StackOverflow社区!

我正在使用可变参数模板,继承和抽象工厂模式进行工作,现在正努力使其协同工作。看来我对这些主题的了解程度是目前为止最遥远的,因此,如果您能给我一个提示或代码示例,您将不胜感激!提前致谢 ;)

这是上下文(我很抱歉!有几行代码...):

我有一个基础

template<typename... Params>
class P
{
    public:
        virtual void compute(Params&... ps) = 0;
        // other things ...

};

派生

template<typename... Params>
class AP : public P<Params...>
{
    public:
        void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
    private:
        void _compute(std::string& str) {std::cout << "AP::compute str " << str << std::endl;}
};
using A = AP<std::string>;

template<typename... Params>
class BP : public P<Params...>
{
    public:
        void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
    private:
        void _compute(int& i) {std::cout << "BP::compute i " << i << std::endl;}
};
using B = BP<int>;

到这里,没有问题!如果我做的很小main(),这将毫无问题地起作用:

int main()
{
    std::unique_ptr<P<int>> p1 = std::make_unique<B>();
    int i = 15;
    p1->compute(i);

    std::unique_ptr<P<std::string>> p2 = std::make_unique<A>();
    std::string str = "abc";
    p2->compute(str);
}

但是,我们可以添加更多:工厂类。(这些将与我的P类以外的其他类一起使用...如果您想知道为什么:))

template<typename Base>
class Creator
{
    public:
        virtual std::unique_ptr<Base> create() = 0;
};

template<class Key, class T>
class Factory
{
    public:
        void store(Key key, std::unique_ptr<Creator<T>>&& creator)
        {
            _crs[key] = std::move(creator);
        }

        std::unique_ptr<T> create(Key key)
        {
            return _crs[key]->create();
        }

    private:
        std::map<Key, std::unique_ptr<Creator<T>>> _crs;
};

及其实现,以构建与P相关的对象:

template<typename Derived, typename... Params>
class PCreator : public Creator<P<Params...>>
{
    public:
        std::unique_ptr<P<Params...>> create() override
        {
            return std::make_unique<Derived>();
        }
};

template<typename... Params>
class PFactory : public Factory<std::string, P<Params...>>
{
    public:
        PFactory()
        {
            this->store("int", std::make_unique<PCreator<BP<int>>>);
            this->store("string", std::make_unique<PCreator<AP<std::string>>>);
        }
        // create() and store() methods inherited
};

如果我实例化PFactory,则编译器显然无法完成其工作,因为它想要PFactory的模板参数将其转发给Factory<std::string, P<Params...>>

但是,相比之下,我的工厂只能创建一个单一的“类型”的P对象,该对象可以使用这些类型Params这就是我一个人走了多远(可惜我的任何一个同事都没有能力帮助我...)

我的目标是能够编写如下内容:

class Thing
{
    const std::array<std::string, 2> a = {"one", "two"};
    public:
        Thing()
        {
            PFactory f;
            for(const auto& e : a)
                _ps[e] = std::move(f.create(e));
        }

        void compute()
        {
            int i = 100;
            std::string str = "qwerty";
            // additional computations...
            _ps["one"]->compute(i);
            // additional computations...
            _ps["two"]->compute(str);
        }

    private:
        std::map<std::string, std::unique_ptr<P>> _ps;
};

这是我尝试在CompilerExplorer上进行工作和返工的PoC ,上面的资源来自何处。

任何帮助将不胜感激!


[编辑]是的,我以为我可以欺骗编译器以创建带有运行时信息的各种方法签名,这是一种幻想。

解决方案总结:

(@walnut:谢谢!)让compute采用std :: any或类似的东西

std::any不太清楚,但是在rtfm-ing CppReference之后,它可以完成工作,接受这样一个事实,即我需要将参数回退到派生类中需要的参数(并处理异常) 。可悲的是,在实际项目中,compute()可以使用多个参数(我使用可变参数模板的原因...我不想关心compute每个Derived类中每个方法中参数的数量或类型),因此它会迫使我创建compute(const std::any&)compute(const std::any&, const std::any&),等等。

(@MaxLanghof:谢谢!)一种(很丑陋的)解决方案是手动提供所有可能的计算重载作为虚拟函数。

是的,您的权利,我也觉得很奇怪(我不会说到“丑陋”,但我还没有更漂亮的解决方案,所以...),但它确实有效。在这里的缺点是,我将无法存储类P(及相关类)在自己的图书馆,因为我想在开始时单独关注(«MainProgram(主程序)»摆弄P期从lib中获得:: P) 。

(@MaxLanghof:谢谢!)在编译时进行整个_ps映射。

我没有足够的C ++经验和知识来实现​​这样的目标。我需要解决这个问题,如果有人有特定的链接(我的意思是:不是Google上的第一个链接;))或示例,我将很高兴学习。

到目前为止,谢谢您的回答!


[编辑]嗨!对不起,抱歉,我刚回到这个项目,非常感谢您的想法和经验!那意义重大 !

我在@Caleth和@KonstantinStupnik的工作中做了一些工作(真的非常感谢您的示例:他们为我提供了很多帮助,帮助我了解了我在做什么!)并通过测试用例达到了这一点:https:// /gcc.godbolt.org/z/AJ8Lsm我遇到了std::bad_any_cast例外,但不明白为什么...

我怀疑传递引用或使用lambda将compute方法保存在中的方式存在问题std::any,但不能确定。我尝试扩展中接收到的类型,AnyCallable<void>::operator()以发现与P的构造函数中的存储函数有所不同,但对我而言似乎相同。

我想传递&AP::computeP的构造函数,但是编译器无法再推断出参数类型...

谢谢大家的时间,帮助和建议!

查看更多

提问者
Psyko
被浏览
23
Caleth 2020-02-03 18:55

您可以键入-擦除参数,只要您可以在呼叫站点指定它们即可。

最少:

#include <functional>
#include <any>
#include <map>
#include <iostream>

template<typename Ret>
struct AnyCallable
{
    AnyCallable() {}
    template<typename F>
    AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
    template<typename ... Args>
    AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
    template<typename ... Args>
    Ret operator()(Args&& ... args) 
    { 
        return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...); 
    }
    template<typename ... Args>
    Ret compute(Args ... args) 
    { 
        return operator()(std::forward<Args>(args)...); 
    }
    std::any m_any;
};

template<>
struct AnyCallable<void>
{
    AnyCallable() {}
    template<typename F>
    AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
    template<typename ... Args>
    AnyCallable(std::function<void(Args...)> fun) : m_any(fun) {}
    template<typename ... Args>
    void operator()(Args&& ... args) 
    { 
        std::invoke(std::any_cast<std::function<void(Args...)>>(m_any), std::forward<Args>(args)...); 
    }
    template<typename ... Args>
    void compute(Args ... args) 
    { 
        operator()(std::forward<Args>(args)...); 
    }
    std::any m_any;
};

using P = AnyCallable<void>;

void A(std::string& str) {std::cout << "AP::compute i " << str << std::endl;}
void B(int i) {std::cout << "BP::compute i " << i << std::endl;}

class Thing
{
    public:
        Thing(){}

        void compute()
        {
            int i = 100;
            std::string str = "qwerty";
            // additional computations...
            ps["one"].compute<int>(i);
            // additional computations...
            ps["two"].compute<std::string&>(str);
        }

    private:
        std::map<std::string, P> ps = { { "one", B }, { "two", A } };
};

或与所有工厂