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

C++中实现具备编译期灵活性的基于特性的设计

实现支持可选特性编译的位掩码设计

问题描述

我需要实现基于特性的设计,每个特性会被添加到类的位掩码FeatureList中。要求即使类依赖的个别特性未被编译,类仍能正常编译且不执行无效操作。

原有写法(存在编译问题)

如果PositionFeature未被编译,下面的代码会直接编译失败:

// 若PositionFeature未编译则无法通过编译!
#include "PositionFeature.h"

class Object
{
    Object()
    {
        FeatureList.AddFlag(RegisteredTypeIndex<PositionFeature>);
    }
};
ENGINE_REGISTER_TYPE(Object)

期望写法(支持可选编译)

即使PositionFeature未被编译,代码仍能正常编译,且调用AddFlag时不执行任何操作:

// 无PositionFeature时也能编译,且不执行任何操作!
class PositionFeature;

class Object
{
    Object()
    {
        AddFeatureFlag<PositionFeature>(FeatureList);
    }
};
ENGINE_REGISTER_TYPE(Object)

当前的类型注册表无法识别未注册但名称一致的特性(会将未定义的类型视为不同特性导致编译失败),需要修改实现以支持这种编译期灵活性。

当前注册表实现代码

#pragma once

#include <cstddef>
#include <type_traits>
#include <utility>

template <typename T>
struct tag { using type = T; };

template <typename...>
struct type_list {};

namespace List
{
    // 返回元素在type_list中的索引,不存在则报错
    template <typename L, typename T>
    struct find_type {};

    template <typename T>
    struct find_type<type_list<>, T> {};
    template <typename F, typename ...P, typename T>
    struct find_type<type_list<F, P...>, T> : std::integral_constant<std::size_t, 1 + find_type<type_list<P...>, T>::value> {};
    template <typename F, typename ...P>
    struct find_type<type_list<F, P...>, F> : std::integral_constant<std::size_t, 0> {};
}

namespace StatefulList
{
    namespace impl
    {
        template <typename Name, std::size_t Index>
        struct ElemReader
        {
        // 屏蔽GCC警告
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
            friend constexpr auto adl_ListElem(ElemReader<Name, Index>);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
        };

        template <typename Name, std::size_t Index, typename Value>
        struct ElemWriter
        {
            friend constexpr auto adl_ListElem(ElemReader<Name, Index>)
            {
                return tag<Value>{};
            }
        };

        constexpr void adl_ListElem() {} // 占位ADL目标

        template <typename Name, std::size_t Index, typename Unique, typename = void>
        struct CalcSize : std::integral_constant<std::size_t, Index> {};

        template <typename Name, std::size_t Index, typename Unique>
        struct CalcSize < Name, Index, Unique, decltype(void(adl_ListElem(ElemReader<Name, Index>{}))) > : CalcSize<Name, Index + 1, Unique> {};

        template <typename Name, std::size_t Index, typename Unique>
        using ReadElem = typename decltype(adl_ListElem(ElemReader<Name, Index>{}))::type;

        template <typename Name, typename I, typename Unique>
        struct ReadElemList {};
        template <typename Name, std::size_t ...I, typename Unique>
        struct ReadElemList<Name, std::index_sequence<I...>, Unique> { using type = type_list<ReadElem<Name, I, Unique>...>; };
    }

    struct DefaultUnique {};

    template <typename T>
    struct DefaultPushBackUnique {};

    // 计算当前列表大小
    template <typename Name, typename Unique = DefaultUnique>
    inline constexpr std::size_t size = impl::CalcSize<Name, 0, Unique>::value;

    // 向列表追加Value
    template <typename Name, typename Value, typename Unique = DefaultPushBackUnique<Value>>
    using PushBack = impl::ElemWriter<Name, size<Name, Unique>, Value>;

    // 返回指定位置的元素类型,不存在则触发SFINAE
    template <typename Name, std::size_t I, typename Unique = DefaultUnique>
    using Elem = impl::ReadElem<Name, I, Unique>;

    // 返回列表所有元素的type_list
    template <typename Name, typename Unique = DefaultUnique>
    using Elems = typename impl::ReadElemList<Name, std::make_index_sequence<size<Name, Unique>>, Unique>::type;
}

// 通用类型计数器标签
struct CounterCommonTag {};

// 返回T的注册索引,未注册则报错
template <typename T>
constexpr std::size_t RegisteredTypeIndex = List::find_type<StatefulList::Elems<CounterCommonTag, T>, T>::value;

// 返回已注册类型总数
// 注意:需在所有注册完成后再使用,否则值会固定;可通过传入不同Unique类型强制重新计算
template <typename Unique = StatefulList::DefaultUnique>
constexpr std::size_t RegisteredTypeCount = StatefulList::size<CounterCommonTag, Unique>;

// 注册类型宏
#define ENGINE_REGISTER_TYPE(type_) static_assert((void(StatefulList::PushBack<CounterCommonTag, type_>{}), true));

解决方案

核心思路是利用**SFINAE(替换失败并非错误)**实现编译期分支:当特性类型未注册或未定义时,跳过AddFlag操作;当类型存在且已注册时,正常添加位掩码。

步骤1:修改List::find_type支持SFINAE

让find_type在类型不存在时不直接报错,而是提供可检测的标记:

namespace List
{
    // 基础模板:默认标记为不存在
    template <typename L, typename T, typename = void>
    struct find_type : std::false_type
    {
        static constexpr std::size_t value = static_cast<std::size_t>(-1); // 用无效索引标记不存在
    };

    // 特化:类型存在于列表头部
    template <typename F, typename ...P>
    struct find_type<type_list<F, P...>, F, void> : std::integral_constant<std::size_t, 0> {};

    // 特化:递归查找剩余列表
    template <typename F, typename ...P, typename T>
    struct find_type<type_list<F, P...>, T, std::enable_if_t<!std::is_same_v<F, T>>>
        : std::integral_constant<std::size_t, 1 + find_type<type_list<P...>, T>::value> {};

    // 辅助模板:检测类型是否在列表中
    template <typename L, typename T>
    constexpr bool contains_type = find_type<L, T>::value != static_cast<std::size_t>(-1);
}

步骤2:添加安全的特性索引获取函数

创建SafeRegisteredTypeIndex模板,未注册类型返回无效值:

// 安全获取类型索引:未注册则返回-1
template <typename T, typename = void>
constexpr std::size_t SafeRegisteredTypeIndex = static_cast<std::size_t>(-1);

template <typename T>
constexpr std::size_t SafeRegisteredTypeIndex<T, std::enable_if_t<List::contains_type<StatefulList::Elems<CounterCommonTag>, T>>>
    = List::find_type<StatefulList::Elems<CounterCommonTag>, T>::value;

步骤3:封装条件添加特性的辅助函数

编写模板函数,仅当类型有效且已注册时调用AddFlag:

// 条件添加特性标记:仅当特性已注册时执行
template <typename Feature>
constexpr void AddFeatureFlag(FeatureList& list)
{
    constexpr auto idx = SafeRegisteredTypeIndex<Feature>;
    if constexpr (idx != static_cast<std::size_t>(-1))
    {
        list.AddFlag(idx);
    }
    // 未注册时无操作
}

步骤4:兼容前向声明类型

修改StatefulList的ReadElem实现,避免前向声明类型触发编译错误:

namespace StatefulList
{
    namespace impl
    {
        // 修改ReadElem,支持前向声明类型的SFINAE检测
        template <typename Name, std::size_t Index, typename Unique, typename = void>
        struct ReadElemHelper
        {
            using type = void; // 占位类型
        };

        template <typename Name, std::size_t Index, typename Unique>
        struct ReadElemHelper<Name, Index, Unique, decltype(void(adl_ListElem(ElemReader<Name, Index>{})))>
        {
            using type = typename decltype(adl_ListElem(ElemReader<Name, Index>{}))::type;
        };

        template <typename Name, std::size_t Index, typename Unique>
        using ReadElem = typename ReadElemHelper<Name, Index, Unique>::type;

        // 过滤type_list中的void类型
        template <typename L>
        struct FilterVoid {};

        template <>
        struct FilterVoid<type_list<>> { using type = type_list<>; };

        template <typename T, typename... Rest>
        struct FilterVoid<type_list<T, Rest...>>
        {
            using type = std::conditional_t<std::is_same_v<T, void>,
                typename FilterVoid<type_list<Rest...>>::type,
                type_list<T, typename FilterVoid<type_list<Rest...>>::type>>;
        };
    }

    // 修改Elems为过滤无效void类型后的列表
    template <typename Name, typename Unique = DefaultUnique>
    using ElemsFiltered = typename impl::FilterVoid<impl::ReadElemList<Name, std::make_index_sequence<size<Name, Unique>>, Unique>::type>::type;
}

// 更新SafeRegisteredTypeIndex依赖为过滤后的列表
template <typename T>
constexpr std::size_t SafeRegisteredTypeIndex<T, std::enable_if_t<List::contains_type<StatefulList::ElemsFiltered<CounterCommonTag>, T>>>
    = List::find_type<StatefulList::ElemsFiltered<CounterCommonTag>, T>::value;

最终使用示例

现在可以直接使用前向声明的特性类型,无需包含头文件:

// 仅前向声明,无需包含PositionFeature.h
class PositionFeature;
class VelocityFeature; // 假设这个特性未被编译

class Object
{
    Object()
    {
        // 仅当PositionFeature已注册时添加标记
        AddFeatureFlag<PositionFeature>(FeatureList);
        // VelocityFeature未注册,此调用无操作
        AddFeatureFlag<VelocityFeature>(FeatureList);
    }
};
ENGINE_REGISTER_TYPE(Object)

关键改动说明

  1. SFINAE支持:通过std::enable_if_t和模板特化,让编译期能够判断类型是否存在于注册表中,避免直接报错。
  2. 无效索引标记:用static_cast<std::size_t>(-1)作为未注册类型的索引,方便在constexpr if中判断分支。
  3. 条件执行:利用C++17的constexpr if实现编译期分支,未注册的特性不会生成任何代码。
  4. 前向声明兼容:修改ReadElem的实现,避免前向声明类型触发编译错误。

内容的提问来源于stack exchange,提问作者ingotangjingle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 02:26:59