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

时间:2016-01-28 19:00:19

标签: c++ c++14 template-meta-programming typetraits

更新

我可以找到T.C.答案的全功能实现on GitHub.

问题:

我正在写一个单元转换库。它目前只是标题而且没有依赖关系,如果可能的话,我想保留它。

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

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将要采用的可变参数的数量做出任何假设。

问题是,因为复合单元只是一堆简单单位相乘,并且乘法是可传递的,为了正确比较复合单位,我需要定义一些类型特征,认为以下两个类是当量:

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组合,模板仍然有效em>和其他复合单元(如果我有正确的特性,我的库的其余部分已经可以处理这种情况)。如果特征也可以分解嵌套的复合单位,并且仍然比较等价,那么它将方式更酷。

示例:

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

3 个答案:

答案 0 :(得分:3)

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

通用SI单元类,其模板参数代表指数:

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>>实际上是同一类型。

答案 1 :(得分:2)

除非我误解了这个问题,否则你要检查两个类型列表是否彼此相等,与类型的排序无关。

示例:

<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;
}

答案 2 :(得分:1)

这是一个我认为符合您要求的解决方案,但有些事情并不是那么好。

以下是代码:

#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;    
}

这基本上可以通过为每个单元使用顶部的#define定义唯一的“ID”,然后使用它们为每个单位计算类似的唯一ID {1}}使用compound_type结构。

明显的缺点是必须为每个单元定义唯一ID。为了避免ID冲突(Unroll),需要巧妙地选择ID,例如使用Jarod42建议的素数。您也可以直接将ID用于结构体,或者如果需要,可以使用模板系统。

第二个缺点是模板化代码不容易阅读。它确实可以完成这项工作。

这是一个live demo,针对问题的奖励部分进行了更新。