【问题标题】:How to allow custom return types of member functions for type erasure in C++?如何在 C++ 中允许自定义返回类型的成员函数进行类型擦除?
【发布时间】:2018-09-26 19:54:20
【问题描述】:

在过去几年中,类型擦除基于概念的运行时多态性变得非常流行,参见例如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 resp 执行此操作。参数类型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::RTypeusing ArgType = typename T::ArgType 的类 T ,我可以让示例工作吗?

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

【问题讨论】:

  • 不清楚你在这里的意思。你想让object_t::draw 根据给定的参数返回不同的值吗?您是否希望将每个T 分配给object_t 能够获取any ArgType,或者您是否希望每个T 能够将特定ArgType 映射到一个特定的ArgType具体RType?如果是后者,您希望object_t 成为ArgType 上的模板,还是希望object_t::draw 强制包含的对象能够接受任何ArgType
  • 您不能不使用 (ii) 或使用协变返回类型规则提供的灵活性。
  • @FrançoisAndrieux 抱歉,我不清楚。目前,任何版本都可以。
  • @NicolBolas 我编辑了这个问题。我希望这个问题现在已经清楚了。

标签: c++ type-erasure


【解决方案1】:

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

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

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

PyObject* func(PyObject*);

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

【讨论】:

    猜你喜欢
    • 2020-04-09
    • 2011-11-17
    • 1970-01-01
    • 2014-11-24
    • 2020-04-17
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多