如何创建共享概念的对象向量?
我想创建一个包含不同类型但共享相同概念的对象的向量(或数组)。
类似于Vec<Box<dyn trait>>Rust。
struct Dog {
void talk() {
std::cout << "guau guau" << std::endl;
}
};
struct Cat {
void talk() {
std::cout << "miau miau" << std::endl;
}
};
template <typename T>
concept Talk = requires(T a) {
{ a.talk() } -> std::convertible_to<void>;
};
int main() {
auto x = Dog{};
auto y = Cat{};
??? pets = {x, y};
for(auto& pet: pets) {
pet.talk();
}
return 0;
}
回答
您要查找的内容通常称为“类型擦除”。C++20concept的语言特性不(也不能)支持类型擦除——该特性完全限于约束模板(类模板、函数模板、类模板的成员函数等),不能真正用于任何其他语境。
您必须改为Talk手动编写可以擦除的类型,或者使用可用的类型擦除库之一。
例如,使用dyno(Louis Dionne 在CppCon 2017和CppNow 2018上进行了多次演讲),这将如下所示。你会注意到我使用这个概念的唯一地方Talk是限制默认概念图:
#include <dyno.hpp>
#include <vector>
#include <iostream>
using namespace dyno::literals;
// this is the "concept" we're going to type erase
struct PolyTalkable : decltype(dyno::requires_(
dyno::CopyConstructible{},
dyno::Destructible{},
"talk"_s = dyno::method<void()>
)) { };
template <typename T>
concept Talk = requires (T a) { a.talk(); };
// this how we implement our "concept"
template <Talk T>
auto const dyno::default_concept_map<PolyTalkable, T> = dyno::make_concept_map(
"talk"_s = [](T& self) { self.talk(); }
);
// this is our hand-written "dyn PolyTalkable"
class DynTalkable {
dyno::poly<PolyTalkable> impl_;
public:
template <typename T>
requires (!std::same_as<T, DynTalkable>
&& dyno::models<PolyTalkable, T>())
DynTalkable(T t) : impl_(t) { }
void talk() {
impl_.virtual_("talk"_s)();
}
};
struct Dog {
void talk() {
std::cout << "guau guau" << std::endl;
}
};
struct Cat {
void talk() {
std::cout << "miau miau" << std::endl;
}
};
int main() {
std::vector<DynTalkable> pets;
pets.push_back(Dog{});
pets.push_back(Cat{});
for (auto& pet : pets) {
pet.talk();
}
}
有关 C++ 类型擦除的其他资源,另请参阅:
- Sean Parent's Inheritance 是 Base Class of Evil和C++ Seasoning 的演讲,无论如何每个人都应该看看。这演示了一种执行运行时多态的机制。
- Sy Brand 的动态多态性与元类(他们在多个会议上发表了这个演讲,最近一次是ACCU 2021)。这演示了如何使用反射工具编写类型擦除库,以便结果比我上面展示的代码少得多。