首页 > 解决方案 > 如何在 C++ 中允许自定义返回类型的成员函数进行类型擦除?

问题描述

在过去的几年里,类型擦除基于概念的运行时多态变得非常流行,参见例如Sean Parent的演讲 Better Code: Runtime Polymorphism , Inheritance Is The Base Class of EvilC++ Seasoning resp。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) 如果RTypeorArgType将是一个显式类型,即存在一个class MyClass{};我们可以定义的类型using RType = MyClass;orusing ArgType = MyClass并且一切都很好。

(ii) 如果RTypeArgType将是基于概念的多态类型,例如object_t它也可以。但这意味着代码库将充满接口类,例如object_t. 不要误会我的意思,这将是一个公平的代价,但需要一些时间来适应它并构建一个代码库来做到这一点。

现在有问题的部分:如果我有这样的东西using RType = typename T::RTypeusing ArgType = typename T::ArgType上课,我可以让这个例子工作吗T???

IE

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(...)

标签: c++type-erasure

解决方案


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

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

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

PyObject* func(PyObject*);

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


推荐阅读