如何在C ++中允许成员函数的自定义返回类型进行类型擦除?

时间:2018-09-26 19:54:20

标签: c++ type-erasure

在过去的几年中,类型擦除基于概念的运行时多态性变得非常流行,例如 Sean Parent Better Code: Runtime PolymorphismInheritance Is The Base Class of EvilC++ Seasoning的谈话。 AdobeFacebookBoost.TypeErasureBoost.tedyno的实现。

以下是肖恩·帕恩特(Sean Parent)演讲的一个例子:

class object_t {
    struct concept_t {
        virtual ~concept_t() = default;
        virtual void draw_(ostream&, size_t) const = 0;
    };
    template <typename T>
    struct model final : concept_t {
        model(T x) : data_(move(x)) { }
        void draw_(ostream& out, size_t position) const override 
        { draw(data_, out, position); }
        T data_;
    };
    shared_ptr<const concept_t> self_;
public:
    template <typename T>
    object_t(T x) : self_(make_shared<model<T>>(move(x))){ }
    friend void draw(const object_t& x, ostream& out, size_t position)
    { x.self_->draw_(out, position); }    
};

在上面的示例中,任何类T都必须提供一个函数

void draw(T data, std::ostream out, size_t position);

对于任何T来说都很容易实现,因为返回类型为void并且参数签名都是已知的编译时间。如果返回类型为int,double,std :: string等,则同样如此。

这是实际的问题:如何针对自定义返回类型RType分别执行此操作。参数类型ArgType

class object_t {
    struct concept_t {
        virtual ~concept_t() = default;
        virtual void draw_(ostream&, size_t) const = 0;
        virtual RType foo (ArgType arg) const = 0; // new function
    };
    template <typename T>
    struct model final : concept_t {
        model(T x) : data_(move(x)) { }
        void draw_(ostream& out, size_t position) const override 
        { draw(data_, out, position); }
        RType foo (ArgType arg) const override 
        { return data_.foo(arg);}; // new function
        T data_;
    };
    shared_ptr<const concept_t> self_;
public:
    template <typename T>
    object_t(T x) : self_(make_shared<model<T>>(move(x))){ }
    friend void draw(const object_t& x, ostream& out, size_t position)
    { x.self_->draw_(out, position); }    
    RType foo (ArgType arg) const 
    { return self_->foo(arg);}; // new function
};

(i)如果RTypeArgType是显式类型,即存在类型class MyClass{};,我们可以定义using RType = MyClass;using ArgType = MyClass以及所有内容很好。

(ii)如果RTypeArgType是基于概念的多态类型,例如object_t,那也很好。 但这意味着代码库将充满诸如object_t之类的接口类。不要误会我的意思,这将是一个公平的代价,但是要习惯它并构建代码库需要花费一些时间。

现在有问题的部分:如果我对类using RType = typename T::RType有类似using ArgType = typename T::ArgTypeT的内容,可以使示例工作吗???

class T {
public:
    using ArgType = /*...*/;
    using RType = /*...*/;
/*...*/
}; 

编辑:

最终目标是开设两个班级

class MyClass {
    using ArgType = /* e.g. int*/; 
    using RType = /* e.g. double*/;
    RType foo (ArgType arg) const {/*...*/;}
};

class YourClass {
    using ArgType = /* e.g. size_t*/; 
    using RType = /* e.g. float*/;
    RType foo (ArgType arg) const {/*...*/;}
};

使得我们可以同时使用object_t,即

object_t myObj(MyClass(/*...*/));
object_t yourObj(YourClass(/*...*/));
// use provided functionality
// ... myObj.foo(...)
// ... yourObj.foo(...)

1 个答案:

答案 0 :(得分:0)

肯定地,object_t::foo的任何一个重载都必须具有单个返回类型,并且有充分的理由:如果客户端可以接收类型的对象,而该对象仅存在于创建{{1} },编译器怎么可能生成代码来处理呢?如果可以将所有各种返回类型都转换为某种类型,则可以将其用作外部返回类型,但这肯定太过局限了。

您当然可以提供至少{em>接受任何类型的object_t函数 template ,但是您将如何实现呢?函数模板不能为foo,这是由于类似的原因,即无法从任何一个翻译单元得知(典型)vtable的大小。您可以将几种已知类型中的每一种路由到其自己的虚拟函数,但是此类函数的列表必须事先知道。或者,您可以再次依赖于将任何参数转换为某种已知类型,但是模板的客户端可以自行完成该操作。

唯一的选择是为参数定义一个接口并返回值以提供和写入所有实现类以及针对该实现类和客户端代码。那当然是多少种脚本语言可以工作:本质上,CPython中的每个内置函数都具有签名

virtual

因此,即使是有状态的C ++可访问的任意Python数据操纵器也可以存储在PyObject* func(PyObject*); 中,而无需定义自己的类型擦除类。