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.
CodePudding user response:
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;
}
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).
CodePudding user response:
Do you have an idea for equivalent code that also works in GCC? The code should work in C 17 with both compilers.
Since you don't want the code on the call side to be changed and the problem is with y i
, you could overload operator
as shown below:
#include <type_traits>
#include <iostream>
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;
//overload operator
template<typename U,int D, typename V> friend U operator ( Unit<U,D>& u, V& v);
};
//define overloaded operator
template<typename U, int D, typename V> U operator ( Unit<U,D>& u, V&v)
{
std::cout<<u.operator U() v;//just for checking the value
return u.operator U() v;
}
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;
}
The output the above program can be seen here.