C++编译期单位系统:单位分组为UnitPower及运算符顺序无关实现
编译期C++单位系统实现方案
需求背景
当前已实现基础的编译期单位相乘逻辑,示例如下:
Scalar<int, M_>{2} * Scalar<int, M_>{2} == Scalar<int, M_, M_>{4};
需要扩展支持以下能力:
- 单位除法逻辑,效果:
Scalar<int, M_, M_>{4} / Scalar<int, M_>{2} == Scalar<int, M_>{2} - 通过
template<typename U, int P> struct UnitPower对同类单位分组,自动累计幂次,支持多单位混合运算 - 所有运算符不依赖单位参数的顺序
- 单位幂次为0时自动省略,所有单位抵消后
Scalar自动退化为底层数值类型
多单位混合运算目标效果:
Scalar<double, UnitPower<M_, 1>, UnitPower<S_, -2>, UnitPower<G_, 1>>{70} / Scalar<double, UnitPower<M_, 1>, UnitPower<S_, -1>>{10} == Scalar<double, UnitPower<S_, -1>, UnitPower<G_, 1>>{7}
核心实现代码
1. 基础元工具实现
#include <type_traits> #include <tuple> struct M_; struct S_; struct G_; template<typename U, int P> struct UnitPower {}; // 单位幂次取反工具 template<typename UP> struct negate_power; template<typename U, int P> struct negate_power<UnitPower<U, P>> { using type = UnitPower<U, -P>; }; template<typename UP> using negate_power_t = typename negate_power<UP>::type; // 裸单位自动包装为UnitPower<U,1>,兼容原有写法 template<typename U> struct wrap_unit { using type = UnitPower<U, 1>; }; template<typename U, int P> struct wrap_unit<UnitPower<U, P>> { using type = UnitPower<U, P>; }; template<typename U> using wrap_unit_t = typename wrap_unit<U>::type; // 合并单个单位到现有单位列表 template<typename U, int P, typename... Existing> struct merge_unit_power { using type = std::tuple<Existing..., UnitPower<U, P>>; }; template<typename U, int P, int P2, typename... Rest> struct merge_unit_power<U, P, UnitPower<U, P2>, Rest...> { static constexpr int new_pow = P + P2; using type = std::conditional_t< new_pow == 0, std::tuple<Rest...>, std::tuple<UnitPower<U, new_pow>, Rest...> >; }; // 递归合并所有单位 template<typename MergedTuple, typename... Remain> struct merge_all_units_impl; template<typename... Merged, typename U, int P, typename... Remain> struct merge_all_units_impl<std::tuple<Merged...>, UnitPower<U, P>, Remain...> { using merged_step = typename merge_unit_power<U, P, Merged...>::type; using type = typename merge_all_units_impl<merged_step, Remain...>::type; }; template<typename MergedTuple> struct merge_all_units_impl<MergedTuple> { using type = MergedTuple; }; template<typename... UPs> using merge_all_units_t = typename merge_all_units_impl<std::tuple<>, wrap_unit_t<UPs>...>::type; // 无序单位相等判断 template<typename... A, typename... B> constexpr bool units_equal_v = std::is_same_v<merge_all_units_t<A...>, merge_all_units_t<B...>>;
2. Scalar类改造
template <typename T, class... RawC> struct Scalar { protected: T value; using C = merge_all_units_t<RawC...>; public: constexpr explicit Scalar(const T value) : value(value) {} // 比较运算符,不依赖单位顺序 template<typename U, typename... D> constexpr auto operator<=>(const Scalar<U, D...> rhs) const { static_assert(units_equal_v<RawC..., D...>, "单位不匹配无法比较"); return value <=> static_cast<U>(rhs.value); } template<typename U, typename... D> constexpr bool operator==(const Scalar<U, D...> rhs) const { static_assert(units_equal_v<RawC..., D...>, "单位不匹配无法比较"); return value == static_cast<T>(rhs.value); } // 乘法运算符,自动合并单位 template<typename U, typename... D> constexpr auto operator*(const Scalar<U, D...> rhs) const { using V = std::common_type_t<T, U>; using merged_units = merge_all_units_t<RawC..., D...>; V res_val = static_cast<V>(value) * static_cast<V>(rhs.value); if constexpr (std::tuple_size_v<merged_units> == 0) { return res_val; } else { return []<typename... Ms>(std::tuple<Ms...>, V val) { return Scalar<V, Ms...>{val}; }(merged_units{}, res_val); } } // 除法运算符,自动合并单位 template<typename U, typename... D> constexpr auto operator/(const Scalar<U, D...> rhs) const { using V = std::common_type_t<T, U>; using neg_d = merge_all_units_t<negate_power_t<wrap_unit_t<D>>...>; using merged_units = merge_all_units_t<RawC..., neg_d>; V res_val = static_cast<V>(value) / static_cast<V>(rhs.value); if constexpr (std::tuple_size_v<merged_units> == 0) { return res_val; } else { return []<typename... Ms>(std::tuple<Ms...>, V val) { return Scalar<V, Ms...>{val}; }(merged_units{}, res_val); } } // 标量除法 template<typename U> constexpr Scalar<std::common_type_t<T, U>, RawC...> operator/(const U rhs) const { using V = std::common_type_t<T, U>; return Scalar<V, RawC...>{static_cast<V>(value) / static_cast<V>(rhs)}; } constexpr explicit operator T() const { return value; } template<typename U> constexpr explicit operator U() const { return static_cast<U>(value); } }; template<typename T> struct Meters : Scalar<T, M_> { using Scalar<T, M_>::Scalar; }; template<typename T> struct Seconds : Scalar<T, S_> { using Scalar<T, S_>::Scalar; };
问题解答
1. 实现与单位顺序无关的运算符
所有单位判断逻辑不直接对比原始参数包,而是先通过merge_all_units_t对单位进行合并排序,只要单位和对应幂次完全一致,无论传入顺序如何,合并后的类型都是相同的,自然实现顺序无关的匹配逻辑。
2. 实现单位分组与自动省略
- 合并单位时,递归匹配相同单位类型,累计幂次,幂次为0时直接从参数包中移除
- 所有运算完成后如果合并后的单位参数包为空,直接返回底层数值类型,不需要再包裹
Scalar - 兼容原有裸单位写法,自动将
M_、S_这类裸类型包装为UnitPower<U, 1>
注:以上实现依赖C++20及以上标准支持
内容的提问来源于stack exchange,提问作者feature_engineer
相关产品推荐
相关产品推荐

