将相同类型的可变参数模板类型分组为 <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 = ?
};

部分,但我真的不知道该怎么做..

我也不确定如何使运算符单元顺序不可知。

学习了如何对乘法运算符的单位进行分组后,除法运算符在单位方面类似于乘法 - 只是在右侧使用负幂。

所以我的问题有两个方面:

  1. 如何使函数单元顺序不可知?
  2. 如何将相似的单元组合成一个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

See it on godbolt.

模板元编程问题的答案是始终使用 Boost.Mp11

所以首先,您可能不想同时支持 Scalar<int, M, M>Scalar<int, Power<M, 2>>,反正前者的价值有限。但我将通过演示如何仅将幂列表(其中一些可能是 Powers)转换为标准化集合(尚未分组)来开始激励 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,我们可以将它们分组。这样做的方法是保持 UnitPowermap。 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>>>>);