I have a templatized TRational class to handle fractional numbers for intergral 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 a unsigned implementation and a (longer) signed implementation ?
I tried to search an answer (without 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;
}
2
Answers
In C++17 with
if constexpr
, if statement init, andstd::gcd
, you can reduce a lot:Live demo
However, in C++11, you should be verbose everywhere, and not sure if it is shorter than your actual one:
Live demo
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.
(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
?