所以 C++ 的概念与 Rust 的不同。
所以 C++ 支持概念,但不支持概念图或自动类型擦除。
概念图采用类型,并将它们映射到概念。类型擦除需要一个类型和一个概念,除了实现概念所需的部分之外,忘记了类型的所有内容。
您似乎想要的是基于概念的值类型的自动类型擦除。
我们不知道。然而。
在 C++ 中有很多类型擦除的例子。最常见的一种是虚拟继承,你有一个接口类,然后指向派生类型的指针被擦除为指向接口类及其方法的指针。
这既麻烦又烦人,但很容易使用。
struct ITalker {
virtual void talk() const = 0;
};
您可能还需要值语义。对此的最低支持如下:
struct ITalker {
virtual void talk() const = 0;
virtual std::unique_ptr<ITalker> clone() const = 0;
virtual ~ITalker() {}
};
然后我们可以创建一个clone_ptr<T> 和clonable<D,B>,给我们:
struct Dog:clonable<Dog, ITalker> {
void talk() const final {
std::cout << "guau guau" << std::endl;
}
};
struct Cat:clonable<Cat, ITalker> {
void talk() const final {
std::cout << "miau miau" << std::endl;
}
};
template <typename T>
concept Talk = requires(T a) {
{ a.talk() } -> std::convertible_to<void>;
};
auto x = Dog{};
auto y = Cat{};
std::vector<std::clone_ptr<ITalker>> pets = {x, y};
for(auto& pet: pets) {
if(pet)
pet->talk();
}
还有更多样板。
然而,这不是你想要的;它对一个人来说是侵入性的。
我们可以go further 并使用样板文件清理指针。这可以得到你
std::vector<Talker> pets = {x, y};
for(auto& pet: pets) {
pet.talk();
}
它看起来更像值。在引擎盖下,它最终成为上面的clone_ptr,并带有一些额外的语法糖。
现在,没有可以让您采用您定义的概念并生成任何代码。
在here 上,我遇到了类似的问题,我们有一个带有开和关概念的灯。
namespace Light {
struct light_tag{};
template<class T>
concept LightClass = requires(T& a) {
{ a.on() };
{ a.off() };
};
void on(light_tag, LightClass auto& light){ light.on(); }
void off(light_tag, LightClass auto& light){ light.off(); }
// also, a `bool` is a light, right?
void on(light_tag, bool& light){ light=true; }
void off(light_tag, bool& light){ light=false; }
template<class T>
concept Light = requires(T& a) {
{ on( light_tag{}, a ) };
{ off( light_tag{}, a ) };
};
void lightController(Light auto& l) {
on(light_tag{}, l);
off(light_tag{}, l);
}
struct SimpleLight {
bool bright = false;
void on() { bright = true; }
void off() { bright = false; }
};
}
上面有“你是灯吗”的概念,但使用light_tag 允许其他类型通过.on 和.off 方法或支持调用on(light_tag, foo) 来“算作灯”和off(light_tag, foo)。
然后我继续在此之上实现 Sean-parent ish 类型 easure:
namespace Light {
struct PolyLightVtable {
void (*on)(void*) = nullptr;
void (*off)(void*) = nullptr;
template<Light T>
static constexpr PolyLightVtable make() {
using Light::on;
using Light::off;
return {
[](void* p){ on( light_tag{}, *static_cast<T*>(p) ); },
[](void* p){ off( light_tag{}, *static_cast<T*>(p) ); }
};
}
template<Light T>
static PolyLightVtable const* get() {
static constexpr auto retval = make<T>();
return &retval;
}
};
struct PolyLightRef {
PolyLightVtable const* vtable = 0;
void* state = 0;
void on() {
vtable->on(state);
}
void off() {
vtable->off(state);
}
template<Light T> requires (!std::is_same_v<std::decay_t<T>, PolyLightRef>)
PolyLightRef( T& l ):
vtable( PolyLightVtable::get<std::decay_t<T>>() ),
state(std::addressof(l))
{}
};
}
使其适应.talk() 非常容易。
添加值语义,生成的多态值类型可以存储在std::vector中;但正如你所看到的,有样板要写。
Sean Parent 在我上面链接的演讲中使用虚函数来减少样板文件(以无法以可移植方式创建免分配引用为代价)。
到目前为止一切顺利。你能行的。欢迎来到图灵焦油坑;问题是,这并不容易。
为了简单起见,您要么需要做一些严肃的元编程,要么找以前做过的人。
例如,我已经编写了多个 poly_anys 来自动化其中的一些。
如果你想要侵入性(让 dog/cat 继承自 Talker),那么https://stackoverflow.com/a/49546808/1774667 是一个简单的版本。
对于类似这样的更高级的语法:
auto talk = make_any_method<void()>{ [](auto& obj){ obj.talk(); };
std::vector< super_any<decltype(talk)> > vec{ Dog{}, Cat{} };
for (auto& e:vec) {
(e->*talk)();
}
你可以使用Type erasing type erasure, `any` questions?
还有更高级的版本。
这太丑了。
正确的做法是从头开始
struct Talker {
void talk();
};
充分描述概念,然后执行以下操作:
using AnyTalker = TypeErase::Value<Talker>;
和
std::vector<AnyTalker> vec{ Cat{}, Dog{} };
for (auto const& e:vec)
e.talk();
但这必须等到c++23。
TL;DR:c++20 中没有简单或内置的方法可以做到这一点。
您可以实现它,或使用库来减少样板,并获得与其相似的语法。
在c++23 中,我们希望能够删除大部分样板文件以执行此操作。语法与您的不完全匹配;它使用结构作为原型,而不是概念。
在任何情况下,类型擦除都不是源自您所写的概念。 C++ 中的概念既是测试,又是太强大,它们不会公开正确的信息来生成代码以使某些东西通过测试。