C++函数族(Functions-Family)最佳实践实现技术问询
C++函数族实现方案优化
前置说明
函数族(Functions-Family)指的是一组可数的函数集合y=f_i(x),其中计数器i的取值范围是0到n-1(n为整数)。
最简示例
我在C++中定义了一个派生结构体,用来实现n=3的函数族:
template<typename Tfloat, size_t n> struct Base{ template<size_t i> bool f(Tfloat* x){ static_assert(i<n); return false; } }; template<typename Tfloat> struct Derived: Base<Tfloat,3>{ template<> bool f<0>(Tfloat* x){ x[3]-=x[2]; return true; } template<> bool f<1>(Tfloat* x){ x[1]*=x[3]; return true; } template<> bool f<2>(Tfloat* x){ if(x[5]==0) return false; x[2]/=x[5]; return true; } };
需求
我希望实现以下目标:
- 通过继承
Base<Tfloat,3>,在编译期强制要求定义该函数族的全部3个函数; - 能够通过
std::index_sequence<n>等方式静态遍历函数f;目前我为每个函数命名为f0、f1……,但需要手动编写if constexpr(i==0){ return f0(x); }这样的包装器来遍历,这是我当前的实现方式; - 能够在
Derived结构体内部编写函数定义。
现存问题
我遇到了诸多问题:
- 无法在
Derived的作用域内特化模板函数; - 甚至无法对未特化的
Derived的模板函数进行特化; - 虽然可以通过静态断言禁止调用
f<i>(当i>=n时),但无法强制要求定义所有i=0到n-1的函数。
备注
- 不同
i对应的函数通常关联性极低,例如火箭第i阶段的物理模型;我倾向于使用size_t i作为模板参数; Derived通常包含运行时依赖参数及由此计算/初始化的数据,因此使用命名空间替代结构体并不合适。
现有方案:函数数组
参考@HolyBlackCat的评论,我实现了以下代码:
#include<iostream> #include<functional> #include<array> template<typename Tfloat, size_t n> class Base{ protected: std::array<std::function<bool(Tfloat*)>,n> f_; public: // 或许可以结合CRTP,给f_的每个元素设置无效默认值,在派生类构造后断言每个f_[i]都已定义? template<size_t i> bool f(Tfloat* x){ return f_[i](x); } // 访问器防止外部修改f_ }; template<typename Tfloat> class Derived: public Base<Tfloat,3>{ public: Derived(){ Base<Tfloat,3>::f_[0] = [this](Tfloat* x){ x[0] *= x[1]; return true; }; } }; template<typename Tfloat> struct NaiveDerived{ bool f0(Tfloat* x){ x[0] *= x[1]; return true; } }; int main(){ Derived<float> d; float x[10]; d.f<0>(x); }
这段代码能满足需求2和3,但我不喜欢这种函数数组的实现方式,因为我们希望将第i个函数的函数体{ x[0]*=x[1]; return true; }直接与模板函数名f<i>(x)绑定,引入f_只是为了避免给每个函数体命名再分配给对应f<i>,更易读的方案应该是直接将第i个函数体与f<i>关联。现寻求更优的实现方案。
更优实现方案
我们可以利用**CRTP(奇异递归模板模式)**结合std::index_sequence实现编译期强制检查,同时让函数体直接与f<i>模板函数绑定,无需额外的函数数组。
核心思路
- 基类通过CRTP关联派生类,借助
std::index_sequence遍历所有索引,在编译期验证每个f<i>是否被实现; - 派生类通过全特化模板函数
f<i>提供具体实现,基类作为统一调用入口; - 用C20的
concept(或C17的SFINAE)检查函数存在性,配合静态断言强制要求实现所有函数。
完整代码实现
#include <utility> #include <type_traits> // C++20 Concept:检查派生类是否拥有f<i>函数 template<typename Derived, size_t i, typename Tfloat> concept HasF = requires(Derived& d, Tfloat* x) { { d.template f<i>(x) } -> std::same_as<bool>; }; template<typename Tfloat, size_t n, typename Derived> struct Base { // 统一调用接口,转发给派生类实现 template<size_t i> bool f(Tfloat* x) { static_assert(i < n, "Index out of bounds for function family"); return static_cast<Derived*>(this)->template f<i>(x); } private: // 编译期检查所有索引对应的函数是否实现 template<size_t... Is> static constexpr bool check_all_impl(std::index_sequence<Is...>) { return (HasF<Derived, Is, Tfloat> && ...); } // 构造时触发静态断言,强制检查所有函数 Base() { static_assert(check_all_impl(std::make_index_sequence<n>()), "Derived class must implement f<i> for all i in 0..n-1"); } // 允许派生类访问构造函数 friend Derived; }; // 派生类示例:实现n=3的函数族 template<typename Tfloat> struct Derived : Base<Tfloat, 3, Derived<Tfloat>> { // 声明模板函数 template<size_t i> bool f(Tfloat* x); // 全特化每个索引对应的函数实现 template<> bool f<0>(Tfloat* x) { x[3] -= x[2]; return true; } template<> bool f<1>(Tfloat* x) { x[1] *= x[3]; return true; } template<> bool f<2>(Tfloat* x) { if (x[5] == 0) return false; x[2] /= x[5]; return true; } }; // 静态遍历函数族的工具函数 template<typename Tfloat, size_t n, typename Derived, size_t... Is> void traverse_all(Derived& obj, Tfloat* x, std::index_sequence<Is...>) { // 展开所有f<Is>调用 (obj.template f<Is>(x), ...); } // 封装遍历接口,简化调用 template<typename Tfloat, size_t n, typename Derived> void traverse_all(Derived& obj, Tfloat* x) { traverse_all<Tfloat, n>(obj, x, std::make_index_sequence<n>()); } // 测试代码 int main() { Derived<float> d; float x[6] = {0, 1, 2, 3, 4, 5}; // 调用单个函数 d.f<0>(x); // 静态遍历所有函数 traverse_all<float, 3>(d, x); }
方案优势
- 编译期强约束:缺失任何一个
f<i>实现都会触发编译错误,无需运行时检查; - 直观的函数绑定:派生类的
f<i>特化直接对应函数逻辑,没有中间层的函数数组,代码可读性更高; - 原生静态遍历:借助
std::index_sequence可以一键遍历所有函数,无需手动编写分支; - 保留运行时状态:派生类可以正常持有成员变量,函数实现中可以使用
this指针访问运行时数据。
兼容C++17的调整
如果需要兼容C++17,可以用SFINAE替代concept来检查函数存在性:
// 替换HasF Concept的SFINAE工具 template<typename Derived, size_t i, typename Tfloat, typename = void> struct HasF : std::false_type {}; template<typename Derived, size_t i, typename Tfloat> struct HasF<Derived, i, Tfloat, std::void_t<decltype(std::declval<Derived>().template f<i>(std::declval<Tfloat*>()))>> : std::is_same<bool, decltype(std::declval<Derived>().template f<i>(std::declval<Tfloat*>()))> {}; // 检查函数时使用HasF<Derived, Is, Tfloat>::value template<size_t... Is> static constexpr bool check_all_impl(std::index_sequence<Is...>) { return (HasF<Derived, Is, Tfloat>::value && ...); }
内容的提问来源于stack exchange,提问作者kaisong
相关产品推荐
相关产品推荐

