是否有一种更优雅的方法可以使用 c++17/c++20 使用“已保存”类型来撤消类型擦除?

Blu*_*une 3 c++ type-erasure

请考虑以下代码片段:

#include <iostream>

template <typename T>
class SaveType {
 public:
  T* allocate() const { return new T; }
  T* cast(void* obj) const { return static_cast<T*>(obj); }
};

int main() {
  int i = 4;
  // "save" the type of the object i in SType:
  SaveType<decltype(i)> SType;

  // do type erasure
  void* z = static_cast<void*>(&i);
  // do stuff with z ...

  // undo type erasure only with the help of SType
  decltype(SType.allocate()) h = SType.cast(z);
  std::cout << *h << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码编译并运行良好,正如您可以在 Godbolt 在线看到的那样。但代码看起来相当笨拙。c++17 或 c++20 中是否有更好的撤消类型擦除的解决方案?

Fak*_*hid 5

除了优雅之外,正如评论中提到的,您的代码片段可以简化如下:

C++11

#include <iostream>

int main() {
  int i = 4;
  // "save" the type of the object i in SType:
  using SType = decltype(i); // or with the older syntax: typedef decltype(i) SType;

  // do type erasure
  void* z = static_cast<void*>(&i);
  // do stuff with z ...

  // undo type erasure only with the help of SType
  auto h = static_cast<SType*>(z); // or with the older less safe C-style syntax: auto h = (SType*)z;
  std::cout << *h << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

C++17

#include <any>
#include <iostream>

int main() {
  int i = 4;
  // "save" the type of the object i in SType:
  using SType = decltype(i);

  // do type erasure
  std::any z = i;
  // do stuff with z ...

  // undo type erasure only with the help of SType
  auto h = std::any_cast<SType>(z);
  std::cout << h << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,您的SaveType类都没有被使用,因为它只在本地范围内工作(不指定类型),因此是多余的。要纠正这个问题,您必须实现@MichaelAaronSafyan 的代码片段

C++14

#include <iostream>
#include <memory>

class SaveType
{
    public:
        virtual ~SaveType(){}
        virtual void* allocate()const=0;
        virtual void* cast(void* obj)const=0;
};

template<typename T> class Type : public SaveType
{
      public:
         virtual void* allocate()const{ return new T; }
         virtual void* cast(void* obj)const{ return static_cast<T*>(obj); }
};

int main() {
  int i = 4;
  // "save" the type of the object i in SType:
  std::unique_ptr<SaveType> SType = std::make_unique<Type<int>>();;

  // do type erasure
  void* z = static_cast<void*>(&i);
  // do stuff with z ...

  // undo type erasure only with the help of SType
  decltype(SType->allocate()) h = SType->cast(z);
  std::cout << typeid(h).name() << std::endl;

  // undo type erasure manually
  auto h2 = *(int*) z;
  std::cout << h2 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

这允许您提前存储SaveType在容器中,从而在多个作用域中使用它,但是(如上所示),它有自己的问题,因为它返回void*而不是T*(因为基类不知道其派生类是什么)做)。


总结一下(还有奖金):

  1. 如果您的实现使用模板但不考虑范围,您将无法访问非本地范围中的类型,因为您必须将其存储在知道它不知道的东西的容器中。

  2. 如果您的实现使用模板但考虑了范围(如上所示),您将无法访问原始类型,因为您必须通过知道它不知道的东西的基类来访问它。

  3. std::type_info奖励:如果您的实现使用std::type_index(C++11) 或std::any::type(C++17),您将能够访问“类型”,但您访问的类型不能用于类型转换。

  4. 超级奖励:如果您的实现使用 aCovariant Return Type您仍然无法访问“类型”,因为它的隐式重新转换是肤浅的


对于实现#1,您只能在删除类型的同一上下文中撤消类型删除。

对于实现#2(如果适用),您可以放弃直接访问,使基类不需要知道它不知道的内容,从而允许派生类对只有它知道的信息进行操作。这就是所谓的"Tell, Don't Ask"原则。

对于实现 #3, all typeiddecltype(C++11) 和std::any::type(C++17) 可以帮助您加快引用可能类型池的过程(除非您确实知道该池由少数类型组成)对于特定类型,我不建议手动编写代码,而是根据同样生成的可能类型列表以编程方式生成它)。

对于实现 #4,只需将其视为死胡同。