I want to have a class representing a unit with some kind of dimension. This should express something like 1.5m^2. A scalar multiplication with some type shall be allowed and a dimensionless unit should behave exactly like the underlying type. Here is my solution:
#include <type_traits>
template<typename T, int Dim>
class Unit {
public:
explicit Unit(T t): _value(t) {}
template<int D = Dim, typename std::enable_if_t<D==0, int> = 0>
operator T() { static_assert(Dim==0, ""); return _value; } //static_assert not necessary, but gives error if template is removed
T _value;
};
template<typename S, typename T, int Dim>
auto operator*(S s, Unit<T,Dim> unit)
{
return Unit<T, Dim>(s * unit._value);
}
auto main() -> int
{
auto i = double{0};
//Scalar test
auto scalar = int{0};
auto x = Unit<double,1>(i);
auto test = scalar * x;
//Conversion test
auto y = Unit<double,0>(i);
return y + i;
}
This works perfectly fine in clang (https://godbolt.org/z/8Pev7W6Y1). However, due to a GCC bug with templated conversion operators (Conversion operator: gcc vs clang), this does not work in GCC.
It is not possible to remove the SFINAE construction because it (correctly) runs into the static_assert
.
Do you have an idea for equivalent code that also works in GCC? The code should work in C++17 with both compilers.
You can use specialization instead of SFINAE. To avoid too much duplication you can move the common parts (anything that does not depend on Dim
) to a base class:
#include <type_traits>
template <typename T>
class base_unit {
public:
explicit base_unit(T t): _value(t) {}
T _value;
};
template<typename T, int Dim>
class Unit : public base_unit<T> {
public:
explicit Unit(T t): base_unit<T>(t) {}
};
template <typename T>
class Unit<T,0> : public base_unit<T> {
public:
explicit Unit(T t) : base_unit<T>(t) {}
operator T() { return base_unit<T>::_value; }
};
template<typename S, typename T, int Dim>
auto operator*(S s, Unit<T,Dim> unit)
{
return Unit<T, Dim>(s * unit._value);
}
auto main() -> int
{
auto i = double{0};
//Scalar test
auto scalar = int{0};
auto x = Unit<double,1>(i);
auto test = scalar * x;
//Conversion test
auto y = Unit<double,0>(i);
return y + i;
}
Live Demo
Note that this is a little old-fashioned and does not consider more modern C++20 approaches (for example the operator T() requires (Dim == 0)
mentioned in a comment).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With