I have a templatized TRational
class to handle fractional numbers for integral types like signed
/unsigned char
/short
/int
/long long int
etc.
This works fine, but some struct functions require different code depending on the template type, like for signed or unsigned. Example:
template<typename T, typename = typename std::enable_if< std::is_integral<T>::value >::type>
struct TRational
{
inline TRational() : m_Numerator(0), m_Denominator(0) { }
inline TRational(T Numerator) : m_Numerator(Numerator), m_Denominator(1) { }
inline TRational(T Numerator, T Denominator) : m_Numerator(Numerator), m_Denominator(Denominator) { }
// Many functions...
inline TRational<T>& ReduceMe()
{
bool Negative = false;
if (std::is_signed<T>::value)
{
if (m_Numerator < 0)
{
Negative = !Negative;
m_Numerator = -m_Numerator;
}
if (m_Denominator < 0)
{
Negative = !Negative;
m_Denominator = -m_Denominator;
}
}
T First = m_Numerator;
T Second = m_Denominator;
// Use Euclidean algorithm to find Greatest Common Divisor
while (Second != 0)
{
T Temp = Second;
Second = First % Second;
First = Temp;
}
if (First > 1)
{
m_Numerator /= First;
m_Denominator /= First;
}
if (std::is_signed<T>::value && Negative)
m_Numerator = -m_Numerator; // Reduced negative Rationals have negative Numerator
return *this;
}
T m_Numerator;
T m_Denominator;
};
int main(int argc, char* argv[])
{
TRational<int> SignedRational(100, -10);
TRational<unsigned short> UnsignedRational(5, 100);
SignedRational.ReduceMe();
UnsignedRational.ReduceMe();
printf("Signed Rational: (%d,%d)\nUnsigned Rational: (%u,%u)\n",
SignedRational.m_Numerator, SignedRational.m_Denominator,
UnsignedRational.m_Numerator, UnsignedRational.m_Denominator);
}
This will compile on Xcode/Visual Studio/gcc (and optimize away as needed) and when run result in
Signed Rational: (-10,1)
Unsigned Rational: (1,20)
However, specially when compiling on Visual Studio (Windows) the compiler will complain:
warning C4146: unary minus operator applied to unsigned type, result still unsigned
message : while compiling class template member function 'TRational<unsigned int,void> &TRational<unsigned int,void>::ReduceMe(void)'
because the compiler will still compile the code within if (std::is_signed<T>::value)
even in the unsigned 'T' case. Note the code within these blocks depends on the type of 'T'.
How can I remove these warnings neatly in c++-11 (can't use constexpr
), preferably by specifying both an unsigned implementation and a (longer) signed implementation?
I tried to search an answer (without if constexpr
from C++17) online, but couldn't find a solution for this case. As the signed
/unsigned
code is not so large, I wouldn't need a specialisation within the function. Ideally I would have hoped to find something like:
// Something with std::is_unsigned<T>::value
inline TRational<T>& ReduceMe() { /*... unsigned case code */ return *this; }
// Something with std::is_signed<T>::value
inline TRational<T>& ReduceMe() { /*... signed case (somewhat longer) code */ return *this; }
I don't see a lot of sense in allowing rationals over unsigned types. But if you really want to, you can. Just use SFINAE to provide separate implementations for signed and unsigned cases.
template <typename K = T>
inline typename std::enable_if<std::is_signed<K>::value, TRational<K>>::type &ReduceMe()
{
// ...
}
template <typename K = T>
inline typename std::enable_if<std::is_unsigned<K>::value, TRational<K>>::type &ReduceMe()
{
// ...
}
(need an extra template parameter here because otherwise the compiler thinks its a pair of overloaded functions that only differ in their return type)
Aside: do you really really want your default constructor to create 0/0
?