为参数顺序不同的模板 类 创建比较特征

create comparison trait for template classes whose parameters are in a different order

更新

我的 fully-featured 实施 T.C。可以找到 on GitHub.

的答案

问题:

我正在编写一个单位转换库。它目前 header-only 并且没有依赖项,如果可能的话我想保留它。

在库中,复合单位被定义为简单单位的模板:

template<class... Units>
struct compound_unit { //...};

为简单起见,将所有类型都视为纯标记,因此我可以将复合单元定义为:

struct meters {};
struct seconds {};

template<class Unit>
inverse { //... };

struct meters_per_second : 
    compound_unit<meters, inverse<seconds>> {};

在这种情况下,inverse 只是表示 1/秒的另一个模板。

虽然可以制作很多复合单元,所以我无法对 compound_unit 将采用的可变参数的数量做出任何假设。

问题是,由于复合单位只是一堆简单单位相乘,乘法是可传递的,为了正确比较复合单位,我需要定义一些 type-trait 考虑以下因素两个 类 等价于:

struct meters_per_second : 
    compound_unit<meters, inverse<seconds>> {};

struct other_meters_per_second : 
    compound_unit<inverse<seconds>, meters> {};

magical_comparison_trait<meters_per_second, other_meters_per_second>::value; // == true

当然,在一般情况下,C++ 永远不能假定不同顺序的模板参数代表相同的类型,所以我想知道是否有可能实现这样的特性,如果可以,如何实现它。

奖励积分

我同意限制 compound_unit 简单单位类型组成,但如果我从 compound_unit 组成,模板仍然有效简单的 其他复合单元的混合(如果我有正确的特征,我的库的其余部分已经可以处理这种情况)。如果特征也可以分解嵌套的复合单元,并且仍然比较等价性,那将 way 更酷。

示例:

struct acceleration_1 : compound_unit<meters_per_second, inverse<second>> {};
struct acceleration_2 : compound_unit<meters, inverse<second>, inverse<second>> {};

除非我误解了问题,否则您想检查两个类型列表是否彼此相等,而与类型的顺序无关。

示例:

<int, char> /*is equal to*/ <char, int>
<int, float, char> /*is equal to*/ <char, int, float>

假设是这种情况,这里有一个可能的天真解决方案,使用 boost::hana

检查两个 compound_unit 的类型列表是否互为子集,并检查类型列表的大小是否相等。

#include <boost/hana.hpp>
using namespace boost;

template <typename... Ts>
struct compound_unit
{
    constexpr auto type_tuple()
    {
        return hana::tuple_t<Ts...>;
    }
};

template <typename TCU0, typename TCU1>
constexpr bool same_compound_unit(TCU0 cu0, TCU1 cu1)
{
    constexpr auto tt0(cu0.type_tuple());
    constexpr auto tt1(cu1.type_tuple());

    return (hana::is_subset(tt0, tt1) && hana::is_subset(tt1, tt0)) &&
           (hana::size(tt0) == hana::size(tt1));
}

int main()
{

    static_assert(same_compound_unit(compound_unit<int, float, char>{},
                      compound_unit<float, char, int>{}),
        "");

    static_assert(!same_compound_unit(compound_unit<int, float, char>{},
                      compound_unit<int, float, char, int>{}),
        "");

    static_assert(same_compound_unit(compound_unit<float, float, char>{},
                      compound_unit<char, float, char>{}),
        "");

    return 0;
}

这是一个我认为可以满足您要求的解决方案,尽管有些地方不太好。

代码如下:

#include <iostream>
#include <string>

#define METERS  1
#define SECONDS 3
#define INV     5

// ------- Start Unroll
// Unroll calculates the value of a compound type
template <typename... Type>
struct Unroll;

template <typename Type1, typename... Types> 
struct Unroll<Type1,Types...> {
    static constexpr int value = Type1::value * Unroll<Types...>::value;
};

template <typename Type>
struct Unroll<Type> {
    static constexpr int value = Type::value;
};

template <>
struct Unroll<> {
    static constexpr int value = 1;
};

// ---------- End Unroll

// Same definitions as in the question

template <typename... Units>
struct compound_unit {
    static constexpr int value = Unroll<Units...>::value;
};

struct meters {
    static constexpr int value = METERS;
};

struct seconds {
    static constexpr int value = SECONDS;
};

template <typename Unit>
struct inverse {
    // The -1 here can be anything, so long as is doesn't result in any of values which are defined at the top
    static constexpr int value = Unit::value * INV;
};

struct mps : compound_unit<meters, inverse<seconds>> {};
struct mps2 : compound_unit<inverse<seconds>, meters> {};

// Does the conversion using the Unroll struct to check that values are the same
template <typename T, typename V>
struct comparison_trait {
    static constexpr bool value = (T::value == V::value);
};

// Update for Bonus:

struct acc : compound_unit<mps, inverse<seconds>> {};
struct acc2 : compound_unit<meters, inverse<seconds>, inverse<seconds>> {};    

int main()
{
    bool check = comparison_trait<mps, mps2>::value;
    std::cout << "MPS check : " << check;

    bool check1 = comparison_trait<acc, acc2>::value;
    std::cout << "ACC check : " << check1;    
}

这基本上是通过定义一个独特的 'ID' 来工作的,使用顶部的 #define 为每个单位,然后使用它们来计算类似的独特 ID 每个 compound_type 使用 Unroll 结构。

明显的缺点是必须为每个单元定义唯一的 ID。为了避免 ID 冲突 (1*4 == 2*2),需要巧妙地选择 ID,例如像 Jarod42 建议的那样使用质数。如果需要,您也可以直接将 ID 处理到结构中或使用模板系统。

第二个缺点是模板化的代码不太容易阅读。不过它确实能完成工作。

这里有一个live demo,更新了题目的bonus部分

is_permutation 是 O(N^2) 当你唯一能做的就是比较相等性,而且它不处理你的其他用例。由于只有这么多 SI 基本单位,更好的方法是规范化您的单位。

通用 SI 单位 class 其模板参数表示指数:

template<class Meter, class Kilogram, class Second,
         class Ampere, class Kelvin, class Candela, class Mole>
struct unit {};

两个单位相乘:

template<class, class> struct unit_multiply_impl;
template<class... Exps1, class... Exps2>
struct unit_multiply_impl<unit<Exps1...>, unit<Exps2...>> {
    using type = unit<std::ratio_add<Exps1, Exps2>...>;
};

template<class U1, class U2>
using unit_multiply = typename unit_multiply_impl<U1, U2>::type;

逆:

template<class U> struct inverse_impl;
template<class... Exps>
struct inverse_impl<unit<Exps...>> {
    using type = unit<std::ratio_multiply<Exps, std::ratio<-1>>...>;
};

template<class U> using inverse = typename inverse_impl<U>::type;

复合 = 将它们全部相乘:

template<class U, class... Us> struct compound_impl;
template<class U> struct compound_impl<U> { using type = U; };
template<class U1, class U2, class...Us>
struct compound_impl<U1, U2, Us...>
    :  compound_impl<unit_multiply<U1, U2>, Us...> {};

template<class U, class... Us>
using compound_unit = typename compound_impl<U, Us...>::type;

测试:

using std::ratio;
using meters = unit<ratio<1>, ratio<0>, ratio<0>, ratio<0>, ratio<0>, ratio<0>, ratio<0>>;
using seconds = unit<ratio<0>, ratio<0>, ratio<1>, ratio<0>, ratio<0>, ratio<0>, ratio<0>>;

using mps = compound_unit<meters, inverse<seconds>>;
using mps = compound_unit<inverse<seconds>, meters>;

using acc = compound_unit<mps, inverse<seconds>>;
using acc = compound_unit<meters, inverse<seconds>, inverse<seconds>>;

这种方法的一大好处是 compound_unit<mps, inverse<seconds>>compound_unit<meters, inverse<seconds>, inverse<seconds>> 实际上是同一类型。