这看起来像一个适配器,而不是 C++ 中使用的特征。
C++ 中的特征就像std::numeric_limits
or std::iterator_traits
。它接受一个类型并返回有关该类型的一些信息。默认实现处理一定数量的情况,您可以将其专门化来处理其他情况。
他编写的代码有一些问题。
-
在 Rust 中,这用于动态调度。模板版本不是动态的。
-
C++ 在值类型上蓬勃发展。对于嵌入引用,这不能是值类型。
-
检查很晚,在鸭子打字时,错误显示在特征代码中,而不是在调用站点上。
另一种方法是使用自由函数和概念以及 ADL。
turn_light_on(foo)
and turn_light_off(foo)
可以通过 ADL 进行默认设置和查找,从而允许自定义现有类型。如果您想避免“一个命名空间”问题,您可以包含一个接口标记。
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; }
};
}
那么我们就有了我们的OddLight
:
namespace Odd {
class OddLight {
public:
void set(bool state);
};
}
我们希望它成为Light
,所以我们这样做:
namespace Odd {
void on(::Light::light_tag, OddLight& odd){ odd.set(true); }
void off(::Light::light_tag, OddLight& odd){ odd.set(false); }
}
then
struct not_a_light{};
如果我们有测试代码:
int main() {
Light::SimpleLight simple;
Odd::OddLight odd;
not_a_light notLight;
Light::lightController(simple);
Light::lightController(odd);
// Light::lightController(notLight); // fails to compile, error is here
}
请注意概念图:
namespace Odd {
void on(::Light::light_tag, OddLight& odd){ odd.set(true); }
void off(::Light::light_tag, OddLight& odd){ odd.set(false); }
}
可以定义为namespace Odd
or namespace Light
.
如果您想将其扩展到动态调度,则必须手动编写类型擦除。
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))
{}
};
}
现在我们可以写:
void foo( Light::PolyLightRef light ) {
light.on();
light.off();
}
我们得到动态调度;的定义foo
可以对呼叫者隐藏。
延伸PolyLightRef
to PolyLightValue
是不是很棘手——我们只需将分配(移动/复制)/构造(移动/复制)/销毁添加到虚函数表中,然后将状态填充到堆中void*
或者在某些情况下使用小缓冲区优化。
现在我们有一个完整的 Rust 式系统,基于动态“特征”的调度,这些特征在入口点进行测试(当您将它们传递为Light auto
or PolyLightYYY
),在特征命名空间中进行定制or在类型的命名空间等中。
我个人很期待c++23具有反射,以及自动化上述一些样板的可能性。
实际上有一个有用的变体网格:
RuntimePoly CompiletimePoly Concepts
PolyLightRef LightRef<T> Light&
PolyLightValue LightValue<T> Light
你可以用类似 Rust 的方式来解决这个问题。
c++17演绎指南可用于制作CompiletimePoly
使用起来不那么烦人:
LightRef ref = light;
可以推论T
为你与
template<class T>
LightRef(T&)->LightRef<T>;
(这可能是为您写的),并在呼叫站点
LightRefTemplateTakingFunction( LightRef{foo} )
带有错误消息的实时示例