I'm implementing compile time unit system, I am able to multiply different units together such that, for example:
Scalar<int, M_>{2} * Scalar<int, M_>{2} == Scalar<int, M_, M_>{4};
I want also to be able to do this:
Scalar<int, M_, M_>{4} / Scalar<int, M_>{2} == Scalar<int, M_>{2};
And I think a good place to start would be to group similar units to a template< typename T, int P> struct UnitPower
type, so that
Scalar<int, UnitPower<M_, 1>>{2} * Scalar<int, UnitPower<M_, 1>>{2} == Scalar<int, UnitPower<M_, 2>>{4};
and
Scalar<int, UnitPower<M_, 2>>{4} / Scalar<int, UnitPower<M_, 1>>{2} == Scalar<int, UnitPower<M_, 1>>{2};
This will come in handy for a more general case:
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}
I would also need to make the operators agnostic to the order of these UnitPowers...
Here's the code so far:
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; };
As you can see, the division operator is currently only defined for same unit (in the same order), and returns just a number (which is the correct return type in this case), or takes just a number, returning the Scalar with no unit modification, which is also the correct behavior.
The multiplication operator just appends the units, and I need it to group them first.
I've added the
template<int P, typename T>
struct UnitPower {};
template<int P, typename T, typename... R>
struct group_units {
//static constexpr type = ?
};
Part, but I don't really know how to go about it..
I'm also not sure how to make the operators unit order agnostic.
After learning how to group the units for the multiplication operator, the division operator would be similar to the multiplication with regards to units - just using negative powers for the right hand side.
So my question is two-fold:
UnitPower<U, int>
structs, and omit units whose power is 0
? (and decay Scalar
to the underlying value type if all UnitPowers are omitted).Pretty challenging... Even though I only did some basic tests below code seems to work, at very least it should give you quite a number of hints how to solve the problem. There's yet pretty much potential for beautifying the code (even though got less with latest edit) and naming of my templates sure is anything else but optimal – but I leave that to you to fix... As a little compensation division is already supplied, too ;)
The idea is always based on the same fundamental principle: We need to recurse into the template arguments to make the type changes we need. Keeping that in mind it should be possible to understand the code below. If questions remain, feel free to leave a comment.
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};
}