我有一个模板化的 TRational 类来处理整数类型的小数,例如有符号/无符号 char/short/int/long long int 等。这工作正常,但某些结构函数需要不同的代码,具体取决于模板类型,例如有符号或未签名。示例:
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);
}
这将在 Xcode/Visual Studio/gcc 上编译(并根据需要进行优化),并且运行结果为
Signed Rational: (-10,1)
Unsigned Rational: (1,20)
但是,特别是在 Visual Studio (Windows) 上编译时,编译器会抱怨:
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)'
因为即使在无符号“T”的情况下,编译器仍然会编译
if (std::is_signed<T>::value)
内的代码。请注意,这些块中的代码取决于“T”的类型。
如何在 C++-11 中巧妙地删除这些警告(不能使用
constexpr
),最好通过指定未签名的实现和(更长的)签名的实现?
我尝试在线搜索答案(没有来自C++17的
constexpr
),但找不到这种情况的解决方案。由于签名/未签名的代码不是很大,因此我不需要在函数内进行专门化。理想情况下,我希望找到类似的东西:
// 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;
}
在C++17中用
if constexpr
、if语句init和std::gcd
,可以减少很多:
#include <numeric> // std::gcd
template<typename T> constexpr T zeroAs = static_cast<T>(0);
constexpr TRational<T>& ReduceMe()
{
if constexpr (std::is_signed_v<T>)
{
// first handle the m_Numerator
if (m_Numerator = m_Numerator < zeroAs<T> ? m_Numerator : -m_Numerator;
// now check for m_Denominator
m_Numerator < zeroAs<T> && m_Denominator < zeroAs<T>)
{
m_Denominator = -m_Denominator;
}
}
T gcd = std::gcd(m_Numerator, m_Denominator);
m_Numerator /= gcd;
m_Denominator /= gcd;
return *this;
}
但是,在 C++11 中,你应该到处都冗长,并且不确定它是否比你的实际长度短:
template<typename Type = T>
void reduceImpl(std::true_type) {
// Handle signed types
m_Numerator = (m_Numerator < static_cast<T>(0)) ? -m_Numerator : m_Numerator;
if (m_Numerator < static_cast<T>(0) && m_Denominator < static_cast<T>(0)) {
m_Denominator = -m_Denominator;
}
}
template<typename Type = T>
void reduceImpl(std::false_type) {
// Handle unsigned types
}
constexpr TRational<T>& ReduceMe()
{
// tag dispatch
reduceImpl(std::integral_constant<bool, std::is_signed<T>::value>());
// Your gcd calc code here!
m_Numerator /= gcd;
m_Denominator /= gcd;
return *this;
}