【问题标题】:Type erasure and variadic templated member function类型擦除和可变参数模板化成员函数
【发布时间】:2015-12-06 21:48:38
【问题描述】:

下面的例子是一个众所周知的习语的一个最小的例子,可能不是很好的例子。
它可以编译,但为了能够将其保持在最低限度,它是如此丑陋,因为问题不在于成语本身。

struct Foo {
    virtual void fn() = 0;
};

template<class T>
struct Bar: public Foo {
    void fn() override {
        T{}.fn();
    }
};

struct S {
    void fn() { }
};

int main() {
    Foo *foo = new Bar<S>{};
    foo->fn();
}

从一个小时前开始,我一直在努力解决的是如何更改它(或者甚至,如果存在替代习语)以引入可变参数模板成员方法。
显然,我不能修改Foo 类的fn 函数,因为它是虚拟的,并且虚拟说明符不与模板一起使用。这同样适用于 Barfn 规范,因为它必须以某种方式覆盖基类中的规范。

注意。

因为我强烈怀疑这个问题可能是有史以来最大的XYProblem之一,我还想简要描述一下实际问题。

我有一个公开两个模板化成员方法的类:

  • 第一个接受不立即使用的模板类T,而是应该以某种方式存储以便以后使用。

  • 第二个接受可变数量的参数(它实际上是一个可变模板成员函数),并且这些参数应该完美地转发给新创建的 T 实例。

嗯,这个问题要复杂得多,但这是一个很好的近似值,应该让您了解目标是什么。

编辑

我猜它在某种程度上类似于高阶函数。
我的意思是,解决问题的方法确实是绑定第一个参数的模板函数,但据我所知,这是不可能的以及我迄今为止探索过的任何其他方法。
任何表达相同概念的可行解决方案?

【问题讨论】:

  • 您更大的目标是以一种我认为不可能的方式结合运行时和编译时的动态性,但我很想看看比我更聪明的人会想出什么。跨度>
  • @zwol 我也有同样的感觉,这就是我问的原因!! :-)
  • 当然,成员函数模板不能是虚拟的
  • @A.S.H 我知道,我不想同时使用模板和虚拟说明符,而是希望目标明确(我的英语远非好,所以请告诉我如果问题不够清楚)。
  • 我向你为解释你的目标所做的努力致敬,但我承认我看不到你到底在寻找什么。这根本不是英语的问题:)。充其量,我会去模板化基类本身,所以它可以有虚拟方法。但我不确定这是否能实现您的最终目标。

标签: c++ templates c++11 variadic-templates type-erasure


【解决方案1】:

我在cmets中提到的是以下方法:

template<typename T> class Factory {

public:
    template<typename ...Args>
    auto construct(Args && ...args)
    {
        return T(std::forward<Args>(args)...);
    }
};

所以现在,您的第一个公开的类方法将是这样的:

template<typename T>
auto getFactory() {

    return Factory<T>();
}

所以:

auto factory=object.getFactory<someClass>();

// Then later:

factory.construct(std::string("Foo"), bar()); // And so on...

你也可以使用operator() 而不是construct(),所以第二部分就变成了:

factory(std::string("Foo"), bar()); // And so on...

正如我所提到的,这并不是真正的类型擦除。您不能在此处使用类型擦除。

考虑了几分钟,这里不能使用类型擦除的原因是因为类型擦除的给定实例必须是“自包含的”或原子的,而您需要做的是打破原子在您的情况下,将擦除分为两个部分或两个类方法。

那是行不通的。根据定义,类型擦除采用类型并“擦除”它。一旦你的第一个函数类型擦除了它的类方法模板参数,你最终得到的是一个不透明的、类型擦除的某种对象。被类型擦除的内容不再可供外界使用。但是您仍然没有对构造函数参数进行类型擦除,这会发生在其他地方。

您可以对模板类和构造函数参数进行类型擦除。您不能分别对模板类和构造函数参数进行类型擦除,然后以某种方式再次对结果进行类型擦除。

如果您想要的类型擦除的两半出现在相同的范围内,那么简单的基于工厂的方法,就像我所概述的那样,将是您可以获得类似于类型擦除的结果的最接近的方法,所以您实际上可以避免类型擦除,而是依赖编译器生成的膨胀。

【讨论】:

  • 由于努力而被赞成,无论如何我不确定它是否能解决问题。除此之外,也许你给了我一个好主意。明天我会试一试,然后再回答更多问题。
  • 正如你已经提到的,它并没有解决问题,原因很简单:要么我需要 T 类型,当我得到 Args... 来创建工厂时,或者我必须以某种方式创建一个不能被类型擦除和类型擦除以将其存储在某处的工厂。
  • 这在 C++ 中根本不可能,因为模板不能是虚拟的。做这样的事情的唯一方法是让类的构造函数接受一个类型擦除的参数包。然后,对类进行类型擦除,并提供一个接受类型擦除参数包的类型擦除虚函数,然后调用构造函数。
【解决方案2】:

我也同意你不能在这里做你想做的事。我将发布我认为最接近的选项(至少是与 SamVarshavchik 的答案不同的关闭选项)。
我不希望这个答案能完全解决你的问题,但希望它能给你一些想法。

    struct Delay // I have no idea what to call this
    {
        template <class T>
        void SetT()
        {
            function_ = [](boost::any params){return T(params);}
        }

        template <class ... Args>
        boost::any GetT(Args ... args)
        {
            return function_(std::make_tuple(args...));
        }

      private:
        std::function<boost::any(boost::any)> function_;
    };

这样做的明显限制是,任何调用GetT 的人都必须知道T 已经是什么,但如果有帮助,您可以查询boost::any 对象以获取其类的type_info。这里的另一个限制是你必须传入T,它接受boost::any 对象并知道如何处理它。如果你不能让T 这样做,那么你可以像这样更改SetT(或创建一个新的成员函数):

    template <class F>
    SetTFactory(F f)
    {
        function_ = f;
    }

然后像这样使用它:

    Delay d;
    d.SetTFactory([](boost::any s){return std::string(boost::any_cast<const char*>(s));});
    auto s = d.GetT("Message");
    assert(s.type() == typeid(std::string));

这当然会带来一系列全新的困难,所以我不知道这个解决方案对你来说有多可行。我认为不管其他任何事情,你都必须重新考虑你的设计。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多