You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 10:12:49