C++17中能否用CRTP为类按需添加同名成员?
在C++17中实现按需添加成员的CRTP类
需求说明
需要实现一个CRTP模板类require_foo,满足:
- 当子类自身已经定义了
int foo成员时,CRTP基类为空,子类仅保留自身的foo; - 当子类没有定义
foo成员时,CRTP基类提供int foo成员。
期望的使用效果:
template<class C> using require_foo = ...; struct Foolish : require_foo<Foolish> { int foo; }; // 仅包含自身定义的foo,CRTP基类为空 struct NotFoolish : require_foo<NotFoolish> { }; // 使用CRTP基类提供的foo
原方案的核心问题
你尝试的实现中,has_foo检测的是包含CRTP基类的完整子类类型,这导致了循环依赖悖论:
- 检测
Foolish是否有foo时,Foolish的完整类型依赖require_foo<Foolish>的实例化,而require_foo又依赖has_foo<Foolish>的结果,此时Foolish自身的foo还未被纳入检测; - 检测
NotFoolish时,CRTP基类还未决定是否添加foo,因此也检测不到任何foo成员,最终两个断言都失败。
解决的关键是:只检测子类自身的成员,完全忽略继承自基类的成员。
可行实现方案
利用C++17的std::void_t和SFINAE特性,实现一个仅检测类自身成员的类型 trait,再基于此实现CRTP类:
1. 实现检测自身成员的类型 trait
#include <type_traits> // 基础模板:默认认为类没有自身的foo成员 template <typename C, typename = void> struct has_own_foo : std::false_type {}; // 特化版本:检测类C是否拥有自身的int类型foo成员 template <typename C> struct has_own_foo<C, std::void_t<decltype(std::declval<C>().foo)>> : std::integral_constant<bool, // 确保foo是int类型 std::is_same_v<decltype(std::declval<C>().foo), int> && // 关键:判断&::foo的类型是否属于C自身,而非基类 std::is_same_v<decltype(&C::foo), int C::*> > {}; // 简化使用的常量模板 template <typename C> constexpr bool has_own_foo_v = has_own_foo<C>::value;
2. 实现CRTP类
// 基础模板:当子类有自身foo时,基类为空 template <typename C, bool = has_own_foo_v<C>> struct require_foo_impl {}; // 特化版本:当子类没有自身foo时,提供foo成员 template <typename C> struct require_foo_impl<C, false> { int foo; }; // 简化使用的别名模板 template <typename C> using require_foo = require_foo_impl<C>;
3. 测试验证
// 测试案例 struct Foolish : require_foo<Foolish> { int foo; }; struct NotFoolish : require_foo<NotFoolish> {}; // 验证检测逻辑 static_assert(has_own_foo_v<Foolish>, "Foolish应包含自身定义的foo"); static_assert(!has_own_foo_v<NotFoolish>, "NotFoolish不应包含自身定义的foo"); // 验证成员可用性 Foolish f; f.foo = 42; // 使用自身的foo NotFoolish n; n.foo = 123; // 使用CRTP基类的foo
方案原理
has_own_foo通过std::void_t检测类是否存在foo成员,再通过decltype(&C::foo)的类型判断该成员是否属于类自身:如果foo是继承来的,&C::foo的类型会是int Base::*(Base为基类),而非int C::*,以此区分自身成员和继承成员;- 这种检测方式避开了循环依赖:在实例化
require_foo<C>时,has_own_foo_v<C>仅依赖C自身的定义,而非继承的CRTP基类,因此能正确判断子类是否有自身的foo成员。
内容的提问来源于stack exchange,提问作者yorel
相关产品推荐
相关产品推荐

