简体   繁体   English

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

[英]Is there a more elegant way for undoing type erasure with a "saved" type using c++17/c++20?

Please consider the following code snippet:请考虑以下代码片段:

#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;
}

The above code compiles and runs fine as you can see online at Godbolt .正如您在 Godbolt 上在线看到的那样,上面的代码可以正常编译和运行。 But the code looks rather clumsy.但是代码看起来相当笨拙。 Is there a better solution for undoing type erasure available in c++17 or c++20? c++17 或 c++20 中是否有更好的撤消类型擦除解决方案?

Elegance aside, as mentioned in the comments your code snippet can be simplified as follows:除了优雅之外,如评论中所述,您的代码片段可以简化如下:

C++11 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;
}

C++17 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;
}

In both cases your SaveType class was not used since it only works (without specifying types) in a local scope and is therefore redundant.在这两种情况下,您的SaveType class 都没有被使用,因为它只在本地 scope 中有效(没有指定类型),因此是多余的。 To rectify this you'd have to implement @MichaelAaronSafyan's code snippet :要纠正此问题,您必须实施@MichaelAaronSafyan 的代码片段

C++14 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;
}

This allows you to store SaveType in a container ahead of time and therefore use it in multiple scopes, however (as demonstrated above), it comes with its own problem in that it returns void* instead of T* (because the base class does not know what its derived class does).这允许您提前将SaveType存储在容器中,因此可以在多个范围内使用它,但是(如上所示),它有自己的问题,因为它返回void*而不是T* (因为基数 class 不知道其派生的 class 的作用)。


To summarize (with a bonus):总结(有奖金):

  1. If your implementation uses templates but does not account for scope you would not be able to access the type in non-local scopes because you would have to store it inside a container that knows something it cannot know.如果您的实现使用模板但不考虑 scope,您将无法在非本地范围内访问该类型,因为您必须将它存储在一个知道它不知道的东西的容器中。

  2. If your implementation uses templates but accounts for scope (as demonstrated above) you would not be able to access the original type because you have to access it through a base class that knows something it cannot know.如果您的实现使用模板但占 scope(如上所示),您将无法访问原始类型,因为您必须通过知道一些它不知道的事情的基础 class 访问它。

  3. Bonus: If your implementation uses std::type_info , std::type_index (C++11) or std::any::type (C++17) you would be able to access the "type" but the type you access cannot be used for type casting.奖励:如果您的实现使用std::type_infostd::type_index (C++11) 或std::any::type (C++17),您将能够访问“类型”但您访问的类型不能用于类型转换。

  4. Super Bonus: If your implementation uses a Covariant Return Type you still would not be able to access the "type" because its implicit reconversion is superficial .超级奖励:如果您的实现使用Covariant Return Type ,您仍然无法访问“类型”,因为它的隐式重新转换是肤浅的


For implementation #1, you can only undo type erasure within the same context in which it was erased.对于实施#1,您只能在擦除它的同一上下文中撤消类型擦除。

For implementation #2, (if applicable) you can forgo direct access and make it so the base class does not need to know what it cannot know thereby allowing the derived class to act on information only it knows.对于实施#2,(如果适用)您可以放弃直接访问并使其成为基础 class 不需要知道它不知道的内容,从而允许派生 class 仅对它知道的信息采取行动。 This is called the, "Tell, Don't Ask" principle.这就是所谓的"Tell, Don't Ask"原则。

For implementation #3, all typeid , decltype (C++11), and std::any::type (C++17) can help you do is expedite the process of referencing a pool of possible types (and unless you know for fact that this pool consists of a handful of specific types, I would not suggest writing the code by hand but instead programmatically generating it based on a likewise generated list of possible types).对于实现 #3,所有typeiddecltype (C++11) 和std::any::type (C++17) 都可以帮助您加快引用可能类型池的过程(除非您知道因为这个池由一些特定类型组成,我不建议手动编写代码,而是基于同样生成的可能类型列表以编程方式生成它)。

For implementation #4, just consider this a dead end.对于实施#4,只需将其视为死胡同。

Funny you should ask this week, because I just happen to be reviewing a paper I wrote in 2015 on "undoing type erasure" .有趣的是你这周应该问,因为我刚好正在审查我在 2015 年写的一篇关于“撤消类型擦除”的论文。

It proposes a generic interface for classes that wrap something like void* , supporting modern C++ usage with const-correctness and lvalue/rvalue categories.它为包装类似void*的类提出了一个通用接口,支持现代 C++ 使用常量正确性和左值/右值类别。 You could give it a try if you are implementing your own type erasure.如果您正在实施自己的类型擦除,您可以尝试一下。

As for erasure classes, they usually do a better job than your simple code, but it's tricky to get 100% right, as you found.至于擦除类,它们通常比您的简单代码做得更好,但正如您发现的那样,要 100% 正确是很棘手的。 Classes like any , variant , and function usually get the job done, but with some rough edges.anyvariantfunction这样的类通常可以完成工作,但有一些粗糙的边缘。 That's what motivated me to write the paper, but motivating others to adopt it is another matter!这就是我写这篇论文的动力,但激励其他人采用它是另一回事!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 c++17 (c++20) 无序 map 和自定义分配器 - c++17 (c++20) unordered map and custom allocator operator==() 代码编译 w/C++17 但不编译 C++20 - operator==() code compiles w/C++17 but not C++20 在 C++17 和 C++20 之间可移植地使用 UTF-8 字符串文字前缀 - Using UTF-8 string-literal prefixes portably between C++17 and C++20 在现代 C++11 / C++14 / C++17 和未来的 C++20 中枚举到字符串 - enum to string in modern C++11 / C++14 / C++17 and future C++20 为什么`std :: reference_wrapper`在c ++ 17中被弃用,而在c ++ 20中被删除? - Why is `std::reference_wrapper` deprecated in c++17 and removed in c++20? 为什么 rebind <U>::other 在 C++17 中被弃用并在 C++20 中被删除?</u> - Why rebind<U>::other are deprecated in C++17 and removed in C++20? 使用C ++ 17检测仿函数的类型特征? - A type trait to detect functors using C++17? 在 C++17 中使用 swig 时目标类型的 cmake 问题 - cmake problem of target type when using swig with C++17 模板友元 function 中的 C++17 和 C++20 与一元和二元运算符的区别 - Difference of C++17 and C++20 in template friend function with unary and binary operators 在 C++17 / C++20 中从 wstring 转换为 u16string 并返回(符合标准) - Conversion from wstring to u16string and back (standard conform) in C++17 / C++20
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM