我可以找到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>> {};
答案 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,针对问题的奖励部分进行了更新。