c++ - 如何在 C++ 中允许自定义返回类型的成员函数进行类型擦除?
问题描述
在过去的几年里,类型擦除或基于概念的运行时多态变得非常流行,参见例如Sean Parent的演讲 Better Code: Runtime Polymorphism , Inheritance Is The Base Class of Evil或C++ Seasoning resp。Adobe、Facebook、Boost.TypeErasure、Boost.te或dyno的实现。
以下是 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) 如果RType
orArgType
将是一个显式类型,即存在一个class MyClass{};
我们可以定义的类型using RType = MyClass;
orusing ArgType = MyClass
并且一切都很好。
(ii) 如果RType
或ArgType
将是基于概念的多态类型,例如object_t
它也可以。但这意味着代码库将充满接口类,例如object_t
. 不要误会我的意思,这将是一个公平的代价,但需要一些时间来适应它并构建一个代码库来做到这一点。
现在有问题的部分:如果我有这样的东西using RType = typename T::RType
或using 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(...)
解决方案
当然,任何一个重载都object_t::foo
必须有一个返回类型,并且有充分的理由:如果客户端可以接收一个类型的对象,该类型只存在于创建 的其他翻译单元中,object_t
编译器怎么可能生成代码来处理它?如果有某种类型可以将所有各种返回类型转换为某种类型,则可以将其用作外部返回类型,但这肯定太局限了。
当然,您可以提供一个至少接受foo
任何类型的函数模板,但是您将如何实现它呢?函数模板不能是,出于类似的原因,(典型)vtable 的大小无法从任何一个翻译单元获知。您可以将几种已知类型中的每一种路由到其自己的虚函数,但必须事先知道此类函数的列表;或者,您可以再次依赖将任何参数转换为某种已知类型,但模板的客户端可以自己执行此操作。virtual
唯一的其他选择是为参数和返回值定义一个接口,以提供和编写所有实现类和客户端代码。这当然是有多少脚本语言可以工作:本质上,CPython 中的每个内置函数都有签名
PyObject* func(PyObject*);
因此,即使是任意 Python 数据的有状态的 C++ 可访问操纵器也可以存储在 a 中,std::function<PyObject*(PyObject*)>
而无需定义您自己的类型擦除类。
推荐阅读
- r - 在 R 中使用 OAuth2 访问 Reddit API:如何请求和更新令牌
- google-cloud-platform - 如何为 gcp 项目添加其他管理员用户以进行 gcp 控制台访问?
- node.js - Node 会自动杀死生成的子进程吗?
- unity3d - Unity HDRP - CustomRenderTexture 不起作用?
- python - 了解加星标的表达
- r - 如何将一个带有下标的特定图例标签略微向下移动?
- java - Get Images from External Storage Image Folder in RecyclerView?
- fpdf - fpdf 在 php 中不支持 utf8_general_ci 排序规则和 utf-8 编码
- tensorflow - 神经网络精度在一些时期后下降到 0
- java - JavaFX FXMLLoader 找不到文件