C++14中如何仅在模板类特定特化版本上声明成员函数
解决方案
C14标准下完全可以通过成员模板SFINAE实现这个需求,不需要依赖C17的if constexpr,也不需要手动写大量类特化,最终可以做到非无量纲类型完全不暴露转换接口,编译期直接拦截非法调用,不会出现你之前遇到的链接期错误、IDE误报的问题。
第一步:实现无量纲判断的编译期谓词
首先定义一个辅助类型特征,用来判断当前Quantity的7个维度指数是否全为0:
#include <type_traits> #include <ratio> template <typename L, typename M, typename T, typename I, typename K, typename N, typename J> using is_dimensionless = std::integral_constant<bool, L::num == 0 && M::num == 0 && T::num == 0 && I::num == 0 && K::num == 0 && N::num == 0 && J::num == 0 >;
这里直接取每个std::ratio的num成员判断是否为0,避免了std::ratio<0,1>和std::ratio<0,2>这类约分前类型不同但实际指数为0的误判问题。
第二步:用SFINAE约束成员函数仅在无量纲时存在
你之前的方案问题在于:主模板中无条件声明了转换函数和getValue,非无量纲版本只是没有实现,所以编译器在语法检查阶段会认为函数存在,直到链接阶段才会报错。
正确的写法是给需要条件存在的成员函数增加独立的模板参数,通过std::enable_if让函数仅在满足无量纲条件时才会被加入重载候选集,效果等同于仅在无量纲特化中声明了这些函数:
template < typename Length = std::ratio<0>, typename Mass = std::ratio<0>, typename Time = std::ratio<0>, typename Current = std::ratio<0>, typename Temperature = std::ratio<0>, typename Amount = std::ratio<0>, typename Luminous = std::ratio<0> > class Quantity { private: double value; public: constexpr Quantity(double val) : value(val) {} Quantity &operator+=(Quantity const &that) { value += that.value; return *this; } // 仅无量纲时存在的隐式转换运算符 // 加Dummy模板参数是为了让SFINAE在调用阶段触发,而非类实例化阶段触发 template <typename Dummy = void, typename = std::enable_if_t< is_dimensionless<Length, Mass, Time, Current, Temperature, Amount, Luminous>::value, Dummy >> constexpr operator double() const { return value; } // 仅无量纲时存在的值获取函数 template <typename Dummy = void, typename = std::enable_if_t< is_dimensionless<Length, Mass, Time, Current, Temperature, Amount, Luminous>::value, Dummy >> constexpr double getValue() const { return value; } // 其余原有运算符实现... };
效果说明
- 对于所有维度不全为0的
Quantity类型,上述两个成员函数会被SFINAE规则直接从类成员列表中剔除,IDE和编译器都会认为这两个函数不存在,非法调用会直接在编译期抛出明确的“无对应成员函数/无合法转换”错误,不会拖到链接阶段。 - 对于全维度为0的无量纲类型,两个函数正常生效,支持隐式转换和值获取。
- 如果你不希望允许隐式转换,只需要在
operator double前加上explicit关键字即可,此时只有显式写static_cast<double>(xxx)时才会触发转换,其余逻辑完全不变。
使用示例
// 常用量纲定义 using Length = Quantity<std::ratio<1>>; using Area = Quantity<std::ratio<2>>; using Scalar = Quantity<>; // 所有维度默认0,为无量纲标量 int main() { Scalar s{2.718}; Length len{3.5}; // 假设已实现operator*,面积为长度*长度 // Area area = len * len; double s_val1 = s; // 合法,隐式转换生效 double s_val2 = s.getValue(); // 合法 // double len_val = len; // 编译直接报错,不存在double转换 // double area_val = area.getValue(); // 编译直接报错,无getValue成员 }
内容的提问来源于stack exchange,提问作者Cameron Tacklind
相关产品推荐
相关产品推荐

