将相同类型的可变参数模板类型分组为 <type, int> 可变参数类型,从可变参数列表中省略 <type, 0>
Grouping same type variadic template types into <type, int> variadic types, omitting <type, 0> from the variadic list
我正在实施编译时间单位系统,我能够将不同的单位相乘,例如:
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 T, int P> struct UnitPower
类型,这样
Scalar<int, UnitPower<M_, 1>>{2} * Scalar<int, UnitPower<M_, 1>>{2} == Scalar<int, UnitPower<M_, 2>>{4};
和
Scalar<int, UnitPower<M_, 2>>{4} / Scalar<int, UnitPower<M_, 1>>{2} == Scalar<int, UnitPower<M_, 1>>{2};
这对于更一般的情况会派上用场:
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}
我还需要让操作员对这些 UnitPowers 的顺序不可知...
目前的代码如下:
struct M_;
struct S_;
struct Mps_;
template<typename T, int P>
struct UnitPower {};
template<typename T, int P, typename... R>
struct group_units {
//static constexpr type = ?
};
template <typename T, class... C>
struct Scalar
{
protected:
T value;
public:
constexpr explicit Scalar(const T value) : value(value) {}
template<typename U>
constexpr auto operator<=>(const Scalar<U, C...> rhs) {
return value <=> static_cast<U>(rhs.value);
}
template<typename U>
constexpr bool operator==(const Scalar<U, C...> rhs) const { return value == static_cast<T>(rhs); }
template<typename U, typename... D>
constexpr Scalar<std::common_type_t<T, U>, C..., D...> operator/(const Scalar<U, D...> rhs) const {
using V = std::common_type_t<T, U>;
return Scalar<V, C..., D...>{static_cast<V>(value) / static_cast<V>(rhs)};
}
template<typename U, typename... D>
constexpr Scalar<std::common_type_t<T, U>, C..., D...> operator*(const Scalar<U, D...> rhs) const {
using V = std::common_type_t<T, U>;
return Scalar<V, C..., D...>{static_cast<V>(value) * static_cast<V>(rhs)};
}
template<typename U>
constexpr std::common_type_t<T, U> operator/(const Scalar<U, C...> rhs) const {
using V = std::common_type_t<T, U>;
return static_cast<V>(value) / static_cast<V>(rhs);
}
template<typename U>
constexpr Scalar<std::common_type_t<T, U>, C...> operator/(const U rhs) const {
using V = std::common_type_t<T, U>;
return Scalar<V, C...>{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; };
如您所见,除法运算符目前只为相同的单位(以相同的顺序)定义,而 return 只是一个数字(这是此中正确的 return 类型例),或者只取一个数字,return 不修改单位的标量,这也是正确的行为。
乘法运算符只是附加单位,我需要它先对它们进行分组。
我添加了
template<int P, typename T>
struct UnitPower {};
template<int P, typename T, typename... R>
struct group_units {
//static constexpr type = ?
};
部分,但我真的不知道该怎么做..
我也不确定如何使运算符单元顺序不可知。
学习了如何对乘法运算符的单位进行分组后,除法运算符在单位方面类似于乘法 - 只是在右侧使用负幂。
所以我的问题有两个方面:
- 如何使函数单元顺序不可知?
- 如何将相似的单元组合成一个
UnitPower<U, int>
结构,并忽略幂为0
的单元? (如果省略所有 UnitPower,则衰减 Scalar
到基础值类型)。
相当具有挑战性...尽管我只做了一些基本测试,但下面的代码似乎有效,至少它应该给你很多提示如何解决问题。美化代码的潜力还很大(尽管最近的编辑减少了),我的模板命名当然不是最佳选择——但我把它留给你来解决……因为已经提供了一个小的补偿部门,也是 ;)
这个想法始终基于相同的基本原则:我们需要 递归 到模板参数中以进行我们需要的类型更改。牢记这一点,应该可以理解下面的代码。如果仍有问题,请随时发表评论。
template <typename Unit, int>
struct UnitPower { };
template <typename T, typename ... Units>
struct Scalar
{
T value;
};
template <typename ... Units>
struct concat;
template <typename ... Units>
using concat_t = typename concat<Units...>::type;
template <typename U, typename ... Units>
struct concat<U, std::tuple<Units...>>
{
using type = std::tuple<U, Units...>;
};
template <typename ... UnitsX, typename ... UnitsY>
struct concat<std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
using type = std::tuple<UnitsX..., UnitsY...>;
};
template <typename ... Units>
struct powers;
template <typename ... Units>
using powers_t = typename powers<Units...>::type;
template <>
struct powers<>
{
using type = std::tuple<>;
};
template <typename U, typename ... Units>
struct powers<U, Units...>
{
using type = concat_t<UnitPower<U, 1>, powers_t<Units...>>;
};
template <typename U, int N, typename ... Units>
struct powers<UnitPower<U, N>, Units...>
{
using type = concat_t<UnitPower<U, N>, powers_t<Units...>>;
};
template <typename ... Units>
struct count;
template <typename ... Units>
using count_t = typename count<Units...>::type;
template <typename U, int P>
struct count<UnitPower<U, P>>
{
using type = UnitPower<U, P>;
};
template <typename U, int PX, int PY, typename ... Units>
struct count<UnitPower<U, PX>, UnitPower<U, PY>, Units...>
{
using type = count_t<UnitPower<U, PX + PY>, Units...>;
};
template <typename UX, int PX, typename UY, int PY, typename ... Units>
struct count<UnitPower<UX, PX>, UnitPower<UY, PY>, Units...>
{
using type = count_t<UnitPower<UX, PX>, Units...>;
};
template < typename ... Units>
struct count<std::tuple<Units...>>
{
using type = count_t<Units...>;
};
template <typename ... Units>
struct remain;
template <typename ... Units>
using remain_t = typename remain<Units...>::type;
template <typename U, int P>
struct remain<UnitPower<U, P>>
{
using type = std::tuple<>;
};
template <typename U, int PX, int PY, typename ... Units>
struct remain<UnitPower<U, PX>, UnitPower<U, PY>, Units...>
{
using type = remain_t<UnitPower<U, PX>, Units...>;
};
template <typename UX, int PX, typename UY, int PY, typename ... Units>
struct remain<UnitPower<UX, PX>, UnitPower<UY, PY>, Units...>
{
using type = concat_t<
UnitPower<UY, PY>,
remain_t<UnitPower<UX, PX>, Units...>
>;
};
template < typename ... Units>
struct remain<std::tuple<Units...>>
{
using type = remain_t<Units...>;
};
template <typename ... Units>
struct combine;
template <typename ... Units>
using combine_t = typename combine<Units...>::type;
template <>
struct combine<>
{
using type = std::tuple<>;
};
template <typename U, int P>
struct combine<UnitPower<U, P>>
{
using type = std::tuple<UnitPower<U, P>>;
};
template <typename U, int P, typename ... Units>
struct combine<UnitPower<U, P>, Units...>
{
using type = concat_t<
count_t<UnitPower<U, P>, Units...>,
combine_t<remain_t<UnitPower<U, P>, Units...>>
>;
};
template <typename ... Units>
struct combine<std::tuple<Units...>>
{
using type = combine_t<Units...>;
};
template <typename ... Units>
struct normalize;
template <typename ... Units>
using normalize_t = typename normalize<Units...>::type;
template <>
struct normalize<>
{
using type = std::tuple<>;
};
template <typename U, typename ... Units>
struct normalize<UnitPower<U, 0>, Units...>
{
using type = normalize_t<Units...>;
};
template <typename U, typename ... Units>
struct normalize<UnitPower<U, 1>, Units...>
{
using type = concat_t<U, normalize_t<Units...>>;
};
template <typename U, int N, typename ... Units>
struct normalize<UnitPower<U, N>, Units...>
{
using type = concat_t<UnitPower<U, N>, normalize_t<Units...>>;
};
template <typename ... Units>
struct normalize<std::tuple<Units...>>
{
using type = normalize_t<Units...>;
};
template <typename T, typename ... Units>
struct scalar;
template <typename ... Units>
using scalar_t = typename scalar<Units...>::type;
template <typename T, typename ... Units>
struct scalar<T, std::tuple<Units...>>
{
using type = Scalar<T, Units...>;
};
template <typename ... T>
struct multiply;
template <typename ... T>
using multiply_t = typename multiply<T...>::type;
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
struct multiply<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
using type = scalar_t<
decltype(std::declval<TX>() * std::declval<TY>()),
normalize_t<combine_t<concat_t<
powers_t<UnitsX...>, powers_t<UnitsY...>
>>>
>;
};
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
auto operator*(Scalar<TX, UnitsX...> x, Scalar<TY, UnitsY...> y)
-> multiply_t<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
return {x.value * y.value};
}
template <typename ... Units>
struct negate;
template <typename ... Units>
using negate_t = typename negate<Units...>::type;
template <>
struct negate<>
{
using type = std::tuple<>;
};
template <typename U, int N, typename ... Units>
struct negate<UnitPower<U, N>, Units...>
{
using type = concat_t<UnitPower<U, -N>, negate_t<Units...>>;
};
template <typename ... Units>
struct negate<std::tuple<Units...>>
{
using type = negate_t<Units...>;
};
template <typename ... T>
struct divide;
template <typename ... T>
using divide_t = typename divide<T...>::type;
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
struct divide<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
using type = scalar_t<
decltype(std::declval<TX>() / std::declval<TY>()),
normalize_t<combine_t<concat_t<
powers_t<UnitsX...>, negate_t<powers_t<UnitsY...>>
>>>
>;
};
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
auto operator/(Scalar<TX, UnitsX...> x, Scalar<TY, UnitsY...> y)
-> divide_t<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
return {x.value / y.value};
}
这是我的想法。
#include <tuple>
#include <type_traits>
template <typename T, typename Tuple>
struct remove_from_tuple;
template <typename T, typename Tuple>
using remove_from_tuple_t = typename remove_from_tuple<T, Tuple>::type;
template <typename T, typename... ElemT>
struct remove_from_tuple<T, std::tuple<ElemT...>> {
using type = decltype(std::tuple_cat(std::declval<
std::conditional_t<std::is_same_v<T, ElemT>, std::tuple<>, std::tuple<ElemT>>
>()...));
static constexpr std::size_t removed =
sizeof...(ElemT) - std::tuple_size<type>::value;
};
template <class Tuple1, class Tuple2>
struct is_tuple_permutation : std::false_type {};
template <class Tuple1, class Tuple2>
constexpr bool is_tuple_permutation_v = is_tuple_permutation<Tuple1, Tuple2>::value;
template <>
struct is_tuple_permutation<std::tuple<>, std::tuple<>>
: public std::true_type {};
template <typename T, typename... List1, typename Tuple2>
struct is_tuple_permutation<std::tuple<T, List1...>, Tuple2> {
private:
using remove1_t = remove_from_tuple<T, std::tuple<List1...>>;
using remove2_t = remove_from_tuple<T, Tuple2>;
public:
static constexpr bool value =
1 + remove1_t::removed == remove2_t::removed &&
is_tuple_permutation_v<typename remove1_t::type, typename remove2_t::type>;
};
struct M_;
struct S_;
struct KG_;
template <class Tag, int P>
struct UnitPower {};
// Trait: UnitPower<Tag0, P0>, UnitPower<Tag1, P1>, ... -> Tag0
template <class... Units>
struct first_tag;
template <class... Units>
using first_tag_t = typename first_tag<Units...>::type;
template <class Tag0, int P0, class... Units>
struct first_tag<UnitPower<Tag0, P0>, Units...> {
using type = Tag0;
};
// Trait: Sum powers of all UnitPower with matching Tag in Units...;
// Put all Units... with different Tag in tuple remainder.
template <class Tag, class... Units>
struct collect_unit;
template <class Tag, class... Units>
constexpr int collect_unit_power = collect_unit<Tag, Units...>::power;
template <class Tag, class... Units>
using collect_unit_remainder_t = typename collect_unit<Tag, Units...>::remainder;
template <class Tag>
struct collect_unit<Tag> {
static constexpr int power = 0;
using remainder = std::tuple<>;
};
template <class Tag, int P0, class... Units>
struct collect_unit<Tag, UnitPower<Tag, P0>, Units...> {
static constexpr int power = P0 + collect_unit_power<Tag, Units...>;
using remainder = collect_unit_remainder_t<Tag, Units...>;
};
template <class Tag, class Unit0, class... Units>
struct collect_unit<Tag, Unit0, Units...> {
static constexpr int power = collect_unit_power<Tag, Units...>;
using remainder = decltype(std::tuple_cat(
std::declval<std::tuple<Unit0>>(),
std::declval<collect_unit_remainder_t<Tag, Units...>>()));
};
// Trait: Combine any units with the same Tag.
template <class Tuple>
struct group_units;
template <class Tuple>
using group_units_t = typename group_units<Tuple>::type;
template <>
struct group_units<std::tuple<>> {
using type = std::tuple<>;
};
template <class... Units>
struct group_units<std::tuple<Units...>> {
private:
using Tag0 = first_tag_t<Units...>;
using collect_t = collect_unit<Tag0, Units...>;
public:
using type = decltype(std::tuple_cat(
std::declval<std::conditional_t<
collect_t::power != 0,
std::tuple<UnitPower<Tag0, collect_t::power>>,
std::tuple<>>>(),
std::declval<group_units_t<typename collect_t::remainder>>()));
};
template <typename T, class... Units>
class Scalar;
// Trait: Do two Scalars have the same underlying type and the same units
// in any order?
template <class S1, class S2>
struct Scalars_compatible : public std::false_type {};
template <class S1, class S2>
constexpr bool Scalars_compatible_v = Scalars_compatible<S1, S2>::value;
template <typename T1, class... Units1, typename T2, class... Units2>
struct Scalars_compatible<Scalar<T1, Units1...>, Scalar<T2, Units2...>>
: public std::bool_constant<is_tuple_permutation_v<
std::tuple<Units1...>, std::tuple<Units2...>>>
{};
template <typename T, class Tuple>
struct tuple_to_Scalar;
template <typename T, class Tuple>
using tuple_to_Scalar_t = typename tuple_to_Scalar<T, Tuple>::type;
template <typename T, class... Units>
struct tuple_to_Scalar<T, std::tuple<Units...>> {
using type = Scalar<T, Units...>;
};
template <class S1, class S2>
struct Scalar_product;
template <class S1, class S2>
using Scalar_product_t = typename Scalar_product<S1, S2>::type;
template <typename T1, class... Units1, typename T2, class... Units2>
struct Scalar_product<Scalar<T1, Units1...>, Scalar<T2, Units2...>> {
using type = tuple_to_Scalar_t<
std::common_type_t<T1, T2>,
group_units_t<std::tuple<Units1..., Units2...>>>;
};
template <class Unit>
struct invert_unit;
template <class Unit>
using invert_unit_t = typename invert_unit<Unit>::type;
template <class Tag, int P>
struct invert_unit<UnitPower<Tag, P>> {
using type = UnitPower<Tag, -P>;
};
template <class S1, class S2>
struct Scalar_quotient;
template <class S1, class S2>
using Scalar_quotient_t = typename Scalar_quotient<S1, S2>::type;
template <typename T1, class... Units1, typename T2, class... Units2>
struct Scalar_quotient<Scalar<T1, Units1...>, Scalar<T2, Units2...>> {
using type = tuple_to_Scalar_t<
std::common_type_t<T1, T2>,
group_units_t<std::tuple<Units1..., invert_unit_t<Units2>...>>>;
};
using Distance_t = Scalar<double, UnitPower<M_, 1>>;
using Time_t = Scalar<double, UnitPower<S_, 1>>;
using Speed_t = Scalar_quotient_t<Distance_t, Time_t>;
using Acceleration_t = Scalar_quotient_t<Speed_t, Time_t>;
using Mass_t = Scalar<double, UnitPower<KG_, 1>>;
using Energy_t = Scalar_product_t<Mass_t, Acceleration_t>;
static_assert(Scalars_compatible_v<Energy_t, Scalar<double, UnitPower<KG_, 1>, UnitPower<M_, 1>, UnitPower<S_, -2>>>);
static_assert(Scalars_compatible_v<Scalar_quotient_t<Speed_t, Acceleration_t>, Time_t>);
请注意,您可能希望将 operator+
、operator==
等限制为需要 Scalars_compatible_v
。
模板元编程问题的答案是始终使用 Boost.Mp11。
所以首先,您可能不想同时支持 Scalar<int, M, M>
和 Scalar<int, Power<M, 2>>
,反正前者的价值有限。但我将通过演示如何仅将幂列表(其中一些可能是 Power
s)转换为标准化集合(尚未分组)来开始激励 Mp11:
// using a type (that is an integral constant)
// for power rather than an integer, for convenience
template <typename Unit, typename Power>
struct UnitPower { };
// if T is a list (it would be a UnitPower)
// otherwise it's a base unit with power 1
template <typename T>
using normalize_unit = mp_if<
mp_is_list<T>, T, UnitPower<T, mp_int<1>>>;
template <typename L>
using normalize = mp_transform<normalize_unit, L>;
我本可以编写此代码以获取一包 T...
并在那里生成一个 mp_list
,但我想更早地开始使用算法。所以在这里,normalize<mp_list<M, M, UnitPower<M, mp_int<2>>>
产生类型 mp_list<UnitPower<M, mp_int<1>>, UnitPower<M, mp_int<1>>, UnitPower<M, mp_int<2>>>
好的,现在我们有了一堆 UnitPower
,我们可以将它们分组。这样做的方法是保持 Unit
到 Power
的 map
。 Mp11 意义上的 map
只是“对”的“列表”,其中每个键只出现一次。但是UnitPower
是Mp11意义上的“pair”,我们要的分组算法基本上就是mp_map_update
的例子:
template <typename M, typename T>
using update_powers = mp_map_update_q<M, T,
mp_bind<mp_plus, _2, mp_second<T>>>;
template <typename L>
using grouped_powers = mp_fold<normalize<L>, mp_list<>, update_powers>;
而且...实际上就是这样。有点尴尬的函数是因为 mp_map_update
接受一个用单位调用的函数,然后是地图中已有元素的幂,所以我们需要提取它的幂 (_2
) 并添加它 (mp_plus
) 到我们当前正在更新的功率 (mp_second<T>
)。
然后,乘法将两个列表组合在一起:
template <typename L1, typename L2>
using multiply_powers = grouped_powers<mp_append<L1, L2>>;
和除法一样,但我们必须先取反右手列表:
template <typename UP>
using negate = mp_replace_second<UP, mp_int<-mp_second<UP>::value>>;
template <typename L1, typename L2>
using divide_powers = multiply_powers<L1, mp_transform<negate, normalize<L2>>>;
这就是我们所需要的。 Demo.
static_assert(std::same_as<
multiply_powers<mp_list<M, M>, mp_list<M, S>>,
mp_list<UnitPower<M, mp_int<3>>, UnitPower<S, mp_int<1>>>>);
static_assert(std::same_as<
divide_powers<mp_list<M, M>, mp_list<M, S>>,
mp_list<UnitPower<M, mp_int<1>>, UnitPower<S, mp_int<-1>>>>);
我正在实施编译时间单位系统,我能够将不同的单位相乘,例如:
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 T, int P> struct UnitPower
类型,这样
Scalar<int, UnitPower<M_, 1>>{2} * Scalar<int, UnitPower<M_, 1>>{2} == Scalar<int, UnitPower<M_, 2>>{4};
和
Scalar<int, UnitPower<M_, 2>>{4} / Scalar<int, UnitPower<M_, 1>>{2} == Scalar<int, UnitPower<M_, 1>>{2};
这对于更一般的情况会派上用场:
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}
我还需要让操作员对这些 UnitPowers 的顺序不可知...
目前的代码如下:
struct M_;
struct S_;
struct Mps_;
template<typename T, int P>
struct UnitPower {};
template<typename T, int P, typename... R>
struct group_units {
//static constexpr type = ?
};
template <typename T, class... C>
struct Scalar
{
protected:
T value;
public:
constexpr explicit Scalar(const T value) : value(value) {}
template<typename U>
constexpr auto operator<=>(const Scalar<U, C...> rhs) {
return value <=> static_cast<U>(rhs.value);
}
template<typename U>
constexpr bool operator==(const Scalar<U, C...> rhs) const { return value == static_cast<T>(rhs); }
template<typename U, typename... D>
constexpr Scalar<std::common_type_t<T, U>, C..., D...> operator/(const Scalar<U, D...> rhs) const {
using V = std::common_type_t<T, U>;
return Scalar<V, C..., D...>{static_cast<V>(value) / static_cast<V>(rhs)};
}
template<typename U, typename... D>
constexpr Scalar<std::common_type_t<T, U>, C..., D...> operator*(const Scalar<U, D...> rhs) const {
using V = std::common_type_t<T, U>;
return Scalar<V, C..., D...>{static_cast<V>(value) * static_cast<V>(rhs)};
}
template<typename U>
constexpr std::common_type_t<T, U> operator/(const Scalar<U, C...> rhs) const {
using V = std::common_type_t<T, U>;
return static_cast<V>(value) / static_cast<V>(rhs);
}
template<typename U>
constexpr Scalar<std::common_type_t<T, U>, C...> operator/(const U rhs) const {
using V = std::common_type_t<T, U>;
return Scalar<V, C...>{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; };
如您所见,除法运算符目前只为相同的单位(以相同的顺序)定义,而 return 只是一个数字(这是此中正确的 return 类型例),或者只取一个数字,return 不修改单位的标量,这也是正确的行为。
乘法运算符只是附加单位,我需要它先对它们进行分组。
我添加了
template<int P, typename T>
struct UnitPower {};
template<int P, typename T, typename... R>
struct group_units {
//static constexpr type = ?
};
部分,但我真的不知道该怎么做..
我也不确定如何使运算符单元顺序不可知。
学习了如何对乘法运算符的单位进行分组后,除法运算符在单位方面类似于乘法 - 只是在右侧使用负幂。
所以我的问题有两个方面:
- 如何使函数单元顺序不可知?
- 如何将相似的单元组合成一个
UnitPower<U, int>
结构,并忽略幂为0
的单元? (如果省略所有 UnitPower,则衰减Scalar
到基础值类型)。
相当具有挑战性...尽管我只做了一些基本测试,但下面的代码似乎有效,至少它应该给你很多提示如何解决问题。美化代码的潜力还很大(尽管最近的编辑减少了),我的模板命名当然不是最佳选择——但我把它留给你来解决……因为已经提供了一个小的补偿部门,也是 ;)
这个想法始终基于相同的基本原则:我们需要 递归 到模板参数中以进行我们需要的类型更改。牢记这一点,应该可以理解下面的代码。如果仍有问题,请随时发表评论。
template <typename Unit, int>
struct UnitPower { };
template <typename T, typename ... Units>
struct Scalar
{
T value;
};
template <typename ... Units>
struct concat;
template <typename ... Units>
using concat_t = typename concat<Units...>::type;
template <typename U, typename ... Units>
struct concat<U, std::tuple<Units...>>
{
using type = std::tuple<U, Units...>;
};
template <typename ... UnitsX, typename ... UnitsY>
struct concat<std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
using type = std::tuple<UnitsX..., UnitsY...>;
};
template <typename ... Units>
struct powers;
template <typename ... Units>
using powers_t = typename powers<Units...>::type;
template <>
struct powers<>
{
using type = std::tuple<>;
};
template <typename U, typename ... Units>
struct powers<U, Units...>
{
using type = concat_t<UnitPower<U, 1>, powers_t<Units...>>;
};
template <typename U, int N, typename ... Units>
struct powers<UnitPower<U, N>, Units...>
{
using type = concat_t<UnitPower<U, N>, powers_t<Units...>>;
};
template <typename ... Units>
struct count;
template <typename ... Units>
using count_t = typename count<Units...>::type;
template <typename U, int P>
struct count<UnitPower<U, P>>
{
using type = UnitPower<U, P>;
};
template <typename U, int PX, int PY, typename ... Units>
struct count<UnitPower<U, PX>, UnitPower<U, PY>, Units...>
{
using type = count_t<UnitPower<U, PX + PY>, Units...>;
};
template <typename UX, int PX, typename UY, int PY, typename ... Units>
struct count<UnitPower<UX, PX>, UnitPower<UY, PY>, Units...>
{
using type = count_t<UnitPower<UX, PX>, Units...>;
};
template < typename ... Units>
struct count<std::tuple<Units...>>
{
using type = count_t<Units...>;
};
template <typename ... Units>
struct remain;
template <typename ... Units>
using remain_t = typename remain<Units...>::type;
template <typename U, int P>
struct remain<UnitPower<U, P>>
{
using type = std::tuple<>;
};
template <typename U, int PX, int PY, typename ... Units>
struct remain<UnitPower<U, PX>, UnitPower<U, PY>, Units...>
{
using type = remain_t<UnitPower<U, PX>, Units...>;
};
template <typename UX, int PX, typename UY, int PY, typename ... Units>
struct remain<UnitPower<UX, PX>, UnitPower<UY, PY>, Units...>
{
using type = concat_t<
UnitPower<UY, PY>,
remain_t<UnitPower<UX, PX>, Units...>
>;
};
template < typename ... Units>
struct remain<std::tuple<Units...>>
{
using type = remain_t<Units...>;
};
template <typename ... Units>
struct combine;
template <typename ... Units>
using combine_t = typename combine<Units...>::type;
template <>
struct combine<>
{
using type = std::tuple<>;
};
template <typename U, int P>
struct combine<UnitPower<U, P>>
{
using type = std::tuple<UnitPower<U, P>>;
};
template <typename U, int P, typename ... Units>
struct combine<UnitPower<U, P>, Units...>
{
using type = concat_t<
count_t<UnitPower<U, P>, Units...>,
combine_t<remain_t<UnitPower<U, P>, Units...>>
>;
};
template <typename ... Units>
struct combine<std::tuple<Units...>>
{
using type = combine_t<Units...>;
};
template <typename ... Units>
struct normalize;
template <typename ... Units>
using normalize_t = typename normalize<Units...>::type;
template <>
struct normalize<>
{
using type = std::tuple<>;
};
template <typename U, typename ... Units>
struct normalize<UnitPower<U, 0>, Units...>
{
using type = normalize_t<Units...>;
};
template <typename U, typename ... Units>
struct normalize<UnitPower<U, 1>, Units...>
{
using type = concat_t<U, normalize_t<Units...>>;
};
template <typename U, int N, typename ... Units>
struct normalize<UnitPower<U, N>, Units...>
{
using type = concat_t<UnitPower<U, N>, normalize_t<Units...>>;
};
template <typename ... Units>
struct normalize<std::tuple<Units...>>
{
using type = normalize_t<Units...>;
};
template <typename T, typename ... Units>
struct scalar;
template <typename ... Units>
using scalar_t = typename scalar<Units...>::type;
template <typename T, typename ... Units>
struct scalar<T, std::tuple<Units...>>
{
using type = Scalar<T, Units...>;
};
template <typename ... T>
struct multiply;
template <typename ... T>
using multiply_t = typename multiply<T...>::type;
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
struct multiply<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
using type = scalar_t<
decltype(std::declval<TX>() * std::declval<TY>()),
normalize_t<combine_t<concat_t<
powers_t<UnitsX...>, powers_t<UnitsY...>
>>>
>;
};
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
auto operator*(Scalar<TX, UnitsX...> x, Scalar<TY, UnitsY...> y)
-> multiply_t<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
return {x.value * y.value};
}
template <typename ... Units>
struct negate;
template <typename ... Units>
using negate_t = typename negate<Units...>::type;
template <>
struct negate<>
{
using type = std::tuple<>;
};
template <typename U, int N, typename ... Units>
struct negate<UnitPower<U, N>, Units...>
{
using type = concat_t<UnitPower<U, -N>, negate_t<Units...>>;
};
template <typename ... Units>
struct negate<std::tuple<Units...>>
{
using type = negate_t<Units...>;
};
template <typename ... T>
struct divide;
template <typename ... T>
using divide_t = typename divide<T...>::type;
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
struct divide<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
using type = scalar_t<
decltype(std::declval<TX>() / std::declval<TY>()),
normalize_t<combine_t<concat_t<
powers_t<UnitsX...>, negate_t<powers_t<UnitsY...>>
>>>
>;
};
template <typename TX, typename TY, typename ... UnitsX, typename ... UnitsY>
auto operator/(Scalar<TX, UnitsX...> x, Scalar<TY, UnitsY...> y)
-> divide_t<TX, TY, std::tuple<UnitsX...>, std::tuple<UnitsY...>>
{
return {x.value / y.value};
}
这是我的想法。
#include <tuple>
#include <type_traits>
template <typename T, typename Tuple>
struct remove_from_tuple;
template <typename T, typename Tuple>
using remove_from_tuple_t = typename remove_from_tuple<T, Tuple>::type;
template <typename T, typename... ElemT>
struct remove_from_tuple<T, std::tuple<ElemT...>> {
using type = decltype(std::tuple_cat(std::declval<
std::conditional_t<std::is_same_v<T, ElemT>, std::tuple<>, std::tuple<ElemT>>
>()...));
static constexpr std::size_t removed =
sizeof...(ElemT) - std::tuple_size<type>::value;
};
template <class Tuple1, class Tuple2>
struct is_tuple_permutation : std::false_type {};
template <class Tuple1, class Tuple2>
constexpr bool is_tuple_permutation_v = is_tuple_permutation<Tuple1, Tuple2>::value;
template <>
struct is_tuple_permutation<std::tuple<>, std::tuple<>>
: public std::true_type {};
template <typename T, typename... List1, typename Tuple2>
struct is_tuple_permutation<std::tuple<T, List1...>, Tuple2> {
private:
using remove1_t = remove_from_tuple<T, std::tuple<List1...>>;
using remove2_t = remove_from_tuple<T, Tuple2>;
public:
static constexpr bool value =
1 + remove1_t::removed == remove2_t::removed &&
is_tuple_permutation_v<typename remove1_t::type, typename remove2_t::type>;
};
struct M_;
struct S_;
struct KG_;
template <class Tag, int P>
struct UnitPower {};
// Trait: UnitPower<Tag0, P0>, UnitPower<Tag1, P1>, ... -> Tag0
template <class... Units>
struct first_tag;
template <class... Units>
using first_tag_t = typename first_tag<Units...>::type;
template <class Tag0, int P0, class... Units>
struct first_tag<UnitPower<Tag0, P0>, Units...> {
using type = Tag0;
};
// Trait: Sum powers of all UnitPower with matching Tag in Units...;
// Put all Units... with different Tag in tuple remainder.
template <class Tag, class... Units>
struct collect_unit;
template <class Tag, class... Units>
constexpr int collect_unit_power = collect_unit<Tag, Units...>::power;
template <class Tag, class... Units>
using collect_unit_remainder_t = typename collect_unit<Tag, Units...>::remainder;
template <class Tag>
struct collect_unit<Tag> {
static constexpr int power = 0;
using remainder = std::tuple<>;
};
template <class Tag, int P0, class... Units>
struct collect_unit<Tag, UnitPower<Tag, P0>, Units...> {
static constexpr int power = P0 + collect_unit_power<Tag, Units...>;
using remainder = collect_unit_remainder_t<Tag, Units...>;
};
template <class Tag, class Unit0, class... Units>
struct collect_unit<Tag, Unit0, Units...> {
static constexpr int power = collect_unit_power<Tag, Units...>;
using remainder = decltype(std::tuple_cat(
std::declval<std::tuple<Unit0>>(),
std::declval<collect_unit_remainder_t<Tag, Units...>>()));
};
// Trait: Combine any units with the same Tag.
template <class Tuple>
struct group_units;
template <class Tuple>
using group_units_t = typename group_units<Tuple>::type;
template <>
struct group_units<std::tuple<>> {
using type = std::tuple<>;
};
template <class... Units>
struct group_units<std::tuple<Units...>> {
private:
using Tag0 = first_tag_t<Units...>;
using collect_t = collect_unit<Tag0, Units...>;
public:
using type = decltype(std::tuple_cat(
std::declval<std::conditional_t<
collect_t::power != 0,
std::tuple<UnitPower<Tag0, collect_t::power>>,
std::tuple<>>>(),
std::declval<group_units_t<typename collect_t::remainder>>()));
};
template <typename T, class... Units>
class Scalar;
// Trait: Do two Scalars have the same underlying type and the same units
// in any order?
template <class S1, class S2>
struct Scalars_compatible : public std::false_type {};
template <class S1, class S2>
constexpr bool Scalars_compatible_v = Scalars_compatible<S1, S2>::value;
template <typename T1, class... Units1, typename T2, class... Units2>
struct Scalars_compatible<Scalar<T1, Units1...>, Scalar<T2, Units2...>>
: public std::bool_constant<is_tuple_permutation_v<
std::tuple<Units1...>, std::tuple<Units2...>>>
{};
template <typename T, class Tuple>
struct tuple_to_Scalar;
template <typename T, class Tuple>
using tuple_to_Scalar_t = typename tuple_to_Scalar<T, Tuple>::type;
template <typename T, class... Units>
struct tuple_to_Scalar<T, std::tuple<Units...>> {
using type = Scalar<T, Units...>;
};
template <class S1, class S2>
struct Scalar_product;
template <class S1, class S2>
using Scalar_product_t = typename Scalar_product<S1, S2>::type;
template <typename T1, class... Units1, typename T2, class... Units2>
struct Scalar_product<Scalar<T1, Units1...>, Scalar<T2, Units2...>> {
using type = tuple_to_Scalar_t<
std::common_type_t<T1, T2>,
group_units_t<std::tuple<Units1..., Units2...>>>;
};
template <class Unit>
struct invert_unit;
template <class Unit>
using invert_unit_t = typename invert_unit<Unit>::type;
template <class Tag, int P>
struct invert_unit<UnitPower<Tag, P>> {
using type = UnitPower<Tag, -P>;
};
template <class S1, class S2>
struct Scalar_quotient;
template <class S1, class S2>
using Scalar_quotient_t = typename Scalar_quotient<S1, S2>::type;
template <typename T1, class... Units1, typename T2, class... Units2>
struct Scalar_quotient<Scalar<T1, Units1...>, Scalar<T2, Units2...>> {
using type = tuple_to_Scalar_t<
std::common_type_t<T1, T2>,
group_units_t<std::tuple<Units1..., invert_unit_t<Units2>...>>>;
};
using Distance_t = Scalar<double, UnitPower<M_, 1>>;
using Time_t = Scalar<double, UnitPower<S_, 1>>;
using Speed_t = Scalar_quotient_t<Distance_t, Time_t>;
using Acceleration_t = Scalar_quotient_t<Speed_t, Time_t>;
using Mass_t = Scalar<double, UnitPower<KG_, 1>>;
using Energy_t = Scalar_product_t<Mass_t, Acceleration_t>;
static_assert(Scalars_compatible_v<Energy_t, Scalar<double, UnitPower<KG_, 1>, UnitPower<M_, 1>, UnitPower<S_, -2>>>);
static_assert(Scalars_compatible_v<Scalar_quotient_t<Speed_t, Acceleration_t>, Time_t>);
请注意,您可能希望将 operator+
、operator==
等限制为需要 Scalars_compatible_v
。
模板元编程问题的答案是始终使用 Boost.Mp11。
所以首先,您可能不想同时支持 Scalar<int, M, M>
和 Scalar<int, Power<M, 2>>
,反正前者的价值有限。但我将通过演示如何仅将幂列表(其中一些可能是 Power
s)转换为标准化集合(尚未分组)来开始激励 Mp11:
// using a type (that is an integral constant)
// for power rather than an integer, for convenience
template <typename Unit, typename Power>
struct UnitPower { };
// if T is a list (it would be a UnitPower)
// otherwise it's a base unit with power 1
template <typename T>
using normalize_unit = mp_if<
mp_is_list<T>, T, UnitPower<T, mp_int<1>>>;
template <typename L>
using normalize = mp_transform<normalize_unit, L>;
我本可以编写此代码以获取一包 T...
并在那里生成一个 mp_list
,但我想更早地开始使用算法。所以在这里,normalize<mp_list<M, M, UnitPower<M, mp_int<2>>>
产生类型 mp_list<UnitPower<M, mp_int<1>>, UnitPower<M, mp_int<1>>, UnitPower<M, mp_int<2>>>
好的,现在我们有了一堆 UnitPower
,我们可以将它们分组。这样做的方法是保持 Unit
到 Power
的 map
。 Mp11 意义上的 map
只是“对”的“列表”,其中每个键只出现一次。但是UnitPower
是Mp11意义上的“pair”,我们要的分组算法基本上就是mp_map_update
的例子:
template <typename M, typename T>
using update_powers = mp_map_update_q<M, T,
mp_bind<mp_plus, _2, mp_second<T>>>;
template <typename L>
using grouped_powers = mp_fold<normalize<L>, mp_list<>, update_powers>;
而且...实际上就是这样。有点尴尬的函数是因为 mp_map_update
接受一个用单位调用的函数,然后是地图中已有元素的幂,所以我们需要提取它的幂 (_2
) 并添加它 (mp_plus
) 到我们当前正在更新的功率 (mp_second<T>
)。
然后,乘法将两个列表组合在一起:
template <typename L1, typename L2>
using multiply_powers = grouped_powers<mp_append<L1, L2>>;
和除法一样,但我们必须先取反右手列表:
template <typename UP>
using negate = mp_replace_second<UP, mp_int<-mp_second<UP>::value>>;
template <typename L1, typename L2>
using divide_powers = multiply_powers<L1, mp_transform<negate, normalize<L2>>>;
这就是我们所需要的。 Demo.
static_assert(std::same_as<
multiply_powers<mp_list<M, M>, mp_list<M, S>>,
mp_list<UnitPower<M, mp_int<3>>, UnitPower<S, mp_int<1>>>>);
static_assert(std::same_as<
divide_powers<mp_list<M, M>, mp_list<M, S>>,
mp_list<UnitPower<M, mp_int<1>>, UnitPower<S, mp_int<-1>>>>);