C++模板实现SI单位类:运行时指数的幂函数实现困境
问题描述
我正在编写一个SI单位类,用整数模板参数表示类型的量纲。编写幂函数时遇到了麻烦:指数未必能在编译期确定,对应的模板参数也没法提前确定。
省略部分代码后的示例:
template <int m = 0, int s = 0, int kg = 0, int A = 0, int K = 0, int mol = 0, int cd = 0> struct Dimension{ template <typename D, Ratio R> friend class Unit; public: real value; template <int m2, int s2, int kg2, int A2, int K2, int mol2, int cd2> constexpr Dimension<m+m2, s+s2, kg+kg2, A+A2, K+K2, mol+mol2, cd+cd2> operator*(Dimension<m2, s2, kg2, A2, K2, mol2, cd2> const & rhs) const {return {value*rhs.value};} // 此处需要实现幂函数 };
乘法运算符可以正常工作,因为返回类型只依赖模板参数。下面几种幂函数写法都无效,因为模板参数依赖运行时参数:
// 写法1:返回类型依赖运行时的n,编译不通过 constexpr auto operator^(int n) const {return Dimension<m*n, s*n, kg*n, A*n, K*n, mol*n, cd*n>{std::pow(value, n)};} // 写法2:只能支持编译期已知的N,无法处理运行时传入的指数 template <int N> constexpr Dimension<m*N, s*N, kg*N, A*N, K*N, mol*N, cd*N> pow() const {return {std::pow(value, N)};} // 写法3:硬编码有限的n值,扩展性极差 constexpr auto operator^(int n) const { if constexpr (n == 0) return Dimension<0, 0, 0, 0, 0, 0, 0>{std::pow(value, 0)}; else if constexpr (n == 1) return Dimension<m*1, s*1, kg*1, A*1, K*1, mol*1, cd*1>{std::pow(value, 1)}; else if constexpr (n == 2) return Dimension<m*2, s*2, kg*2, A*2, K*2, mol*2, cd*2>{std::pow(value, 2)}; // ... 最多覆盖[-10,10]的范围,但理论上应该支持任意整数n }
请问有没有不借助宏的解决方案?
解决方案
思路1:用std::variant封装限定范围内的量纲类型
如果能接受预先限定指数范围(比如你提到的[-10,10]),可以用std::variant把所有可能的Dimension特化类型打包成返回值,运行时根据指数选择对应分支:
#include <variant> #include <cmath> #include <stdexcept> // 定义覆盖目标指数范围的变体类型 template<int m, int s, int kg, int A, int K, int mol, int cd> using PowerVariant = std::variant< Dimension<m*0, s*0, kg*0, A*0, K*0, mol*0, cd*0>, Dimension<m*1, s*1, kg*1, A*1, K*1, mol*1, cd*1>, Dimension<m*2, s*2, kg*2, A*2, K*2, mol*2, cd*2>, Dimension<m*-1, s*-1, kg*-1, A*-1, K*-1, mol*-1, cd*-1>, Dimension<m*-2, s*-2, kg*-2, A*-2, K*-2, mol*-2, cd*-2> // 按需添加更多指数对应的类型 >; template <int m, int s, int kg, int A, int K, int mol, int cd> struct Dimension{ // ... 原有代码 ... constexpr PowerVariant<m,s,kg,A,K,mol,cd> operator^(int n) const { switch(n){ case 0: return Dimension<0,0,0,0,0,0,0>{std::pow(value, 0)}; case 1: return Dimension<m*1,s*1,kg*1,A*1,K*1,mol*1,cd*1>{std::pow(value, 1)}; case 2: return Dimension<m*2,s*2,kg*2,A*2,K*2,mol*2,cd*2>{std::pow(value, 2)}; case -1: return Dimension<m*-1,s*-1,kg*-1,A*-1,K*-1,mol*-1,cd*-1>{std::pow(value, -1)}; case -2: return Dimension<m*-2,s*-2,kg*-2,A*-2,K*-2,mol*-2,cd*-2>{std::pow(value, -2)}; default: throw std::invalid_argument("Unsupported exponent"); } } };
这种方法保留了编译期量纲检查,仅需手动维护有限的指数分支。
思路2:将量纲移至运行时存储
如果必须支持任意整数指数,只能放弃模板参数表示量纲,改用运行时成员变量存储量纲信息。代价是失去编译期类型安全,但彻底解决了运行时指数的问题:
#include <cmath> struct Dimension{ int m, s, kg, A, K, mol, cd; real value; constexpr Dimension(int m_=0, int s_=0, int kg_=0, int A_=0, int K_=0, int mol_=0, int cd_=0, real val=0) : m(m_), s(s_), kg(kg_), A(A_), K(K_), mol(mol_), cd(cd_), value(val) {} constexpr Dimension operator*(const Dimension& rhs) const { return {m+rhs.m, s+rhs.s, kg+rhs.kg, A+rhs.A, K+rhs.K, mol+rhs.mol, cd+rhs.cd, value*rhs.value}; } constexpr Dimension operator^(int n) const { return {m*n, s*n, kg*n, A*n, K*n, mol*n, cd*n, std::pow(value, n)}; } };
思路3:用C++20特性自动生成指数分支
利用C++20的模板lambda和std::integer_sequence,可以自动生成指定范围内的所有指数分支,避免手动硬编码:
#include <utility> #include <cmath> #include <variant> #include <stdexcept> // 生成指定指数范围的变体类型 template<int BaseM, int BaseS, int BaseKG, int BaseA, int BaseK, int BaseMol, int BaseCD, int Min, int Max, int... Is> constexpr auto make_power_variant_impl(std::integer_sequence<int, Is...>) { return std::variant<Dimension<BaseM*Is, BaseS*Is, BaseKG*Is, BaseA*Is, BaseK*Is, BaseMol*Is, BaseCD*Is>...>{}; } template<int BaseM, int BaseS, int BaseKG, int BaseA, int BaseK, int BaseMol, int BaseCD, int Min, int Max> using PowerVariant = decltype(make_power_variant_impl<BaseM, BaseS, BaseKG, BaseA, BaseK, BaseMol, BaseCD, Min, Max>( std::make_integer_sequence<int, Max - Min + 1>{} )); template <int m, int s, int kg, int A, int K, int mol, int cd> struct Dimension{ // ... 原有代码 ... constexpr auto operator^(int n) const -> PowerVariant<m,s,kg,A,K,mol,cd,-10,10> { auto dispatch = [this]<int N>(std::integral_constant<int, N>) { return Dimension<m*N, s*N, kg*N, A*N, K*N, mol*N, cd*N>{std::pow(value, N)}; }; if (-10 <= n && n <=10) { return [&]<int... Is>(std::integer_sequence<int, Is...>) { return ((n == Is ? dispatch(std::integral_constant<int, Is>{}) : std::variant_alternative_t<0, PowerVariant<m,s,kg,A,K,mol,cd,-10,10>>{}), ...); }(std::make_integer_sequence<int, 21>{}); // 覆盖-10到10共21个值 } else { throw std::invalid_argument("Exponent out of range"); } } };
这种方法自动生成指定范围的所有分支,既保持编译期类型安全,又无需手动编写每个指数的处理逻辑。
内容的提问来源于stack exchange,提问作者Nathan29006781
相关产品推荐
相关产品推荐

