I have the below templates:
One is used for unsigned and the other for signed. Is there any elegant way to get rid of the compiler warning without suppressing it?
warning: comparison between signed and unsigned integer expressions
Would I need to write the function for each type, e.g. uint8, uint16 etc..?
template<typename X,typename Y,typename Z, typename std::enable_if<std::is_unsigned<X>::value, bool>::type = true >
void debugValidateParameter( X aValueToCheck, Y aLowerLimit, Z aUpperLimit)
{
if( (aValueToCheck > aUpperLimit) || (aValueToCheck < aLowerLimit) )
{
log("ERROR: ValidateParameters, aValueToCheck = % , aLowerLimit= % , aUpperLimit= % \n", aValueToCheck, aLowerLimit, aUpperLimit );
throw(std::out_of_range("Invalid Range"));
}
}
template<typename X,typename Y,typename Z, typename std::enable_if<std::is_signed<X>::value, bool>::type = true >
void debugValidateParameter( X aValueToCheck, Y aLowerLimit, Z aUpperLimit)
{
if( (aValueToCheck > aUpperLimit) || (aValueToCheck < aLowerLimit) )
{
log("ERROR: ValidateParameters, aValueToCheck = % , aLowerLimit= % , aUpperLimit= % \n", aValueToCheck, aLowerLimit, aUpperLimit );
throw(std::out_of_range("Invalid Range"));
}
}
Let me explain a bit what you got wrong here.
For me, it looks like you would normally want to use the same type for all three parameters. The most straight-forward solution would be this definition:
template<typename X>
void debugValidateParameter( X aValueToCheck, X aLowerLimit, X aUpperLimit)
{
if( (aValueToCheck > aUpperLimit) || (aValueToCheck < aLowerLimit) )
{
log("ERROR: ValidateParameters, aValueToCheck = % , aLowerLimit= % , aUpperLimit= % \n", aValueToCheck, aLowerLimit, aUpperLimit );
throw(std::out_of_range("Invalid Range"));
}
}
But if you then call that function with an unsigned variable and two literal integers, for example:
debugValidateParameter(someUnsignedInteger, 0, 100);
you would get an error since the type cannot be deduced -- for this, all parameters with type X
need to be passed a value of the exact same type.
So deducing the type X
is ambiguous and thus not possible. For me, it looks like you want to deduce the type based on the first parameter being passed (the "actual value") and simply try to convert the bounds to that same type. In other words, something which doesn't force you to write
debugValidateParameter(someUnsignedInteger, 0u, 100u);
This can be done by disabling type deduction for the second and third parameter, by specifying their type as identity_t<X>
instead of just X
, where identity_t
is defined as
template<typename T>
struct identity { typedef T type; };
template<typename T>
using identity_t = typename identity<T>::type;
So your function definition then becomes
template<typename X>
void debugValidateParameter( X aValueToCheck, identity_t<X> aLowerLimit, identity_t<X> aUpperLimit)
{
if( (aValueToCheck > aUpperLimit) || (aValueToCheck < aLowerLimit) )
{
log("ERROR: ValidateParameters, aValueToCheck = % , aLowerLimit= % , aUpperLimit= % \n", aValueToCheck, aLowerLimit, aUpperLimit );
throw(std::out_of_range("Invalid Range"));
}
}
Here you can see the code in a Live Demo.
You don't need SFINAE or specialization, you just need that X
, Y
, Z
have same sign. so you might use
template<typename T>
void debugValidateParameter(T aValueToCheck, T aLowerLimit, T aUpperLimit)
{
if( (aValueToCheck > aUpperLimit) || (aValueToCheck < aLowerLimit) )
{
log("ERROR: ValidateParameters, aValueToCheck = % , aLowerLimit= % , aUpperLimit= % \n",
aValueToCheck, aLowerLimit, aUpperLimit);
throw(std::out_of_range("Invalid Range"));
}
}
but that requires that all arguments are deduced to the same type.
To avoid that, you may force some argument to be non deducible:
template <typename T> struct non_deducible { using type = T; };
template <typename T> using non_deducible_t = typename non_deducible<T>::type;
template<typename T>
void debugValidateParameter(T aValueToCheck,
non_deducible_t<T> aLowerLimit,
non_deducible_t<T> aUpperLimit)
{
if( (aValueToCheck > aUpperLimit) || (aValueToCheck < aLowerLimit) )
{
log("ERROR: ValidateParameters, aValueToCheck = % , aLowerLimit= % , aUpperLimit= % \n",
aValueToCheck, aLowerLimit, aUpperLimit);
throw(std::out_of_range("Invalid Range"));
}
}
How about something like this?
#include <iostream>
#include <type_traits>
#include <stdexcept>
template <typename T1, typename T2>
bool integral_less_than(T1 t1, T2 t2)
{
static_assert(std::is_integral<T1>::value, "");
static_assert(std::is_integral<T2>::value, "");
// Handle different signedness.
if (std::is_unsigned<T1>::value)
{
if (!std::is_unsigned<T2>::value)
return (t2 < 0) ? false : t1 < static_cast<typename std::make_unsigned<T2>::type>(t2);
}
else
{
if (std::is_unsigned<T2>::value)
return (t1 < 0) ? true : static_cast<typename std::make_unsigned<T1>::type>(t1) < t2;
}
// Handle same signedness.
return t1 < t2;
}
template <typename X, typename Y, typename Z>
void ValidateParameter(X aValueToCheck, Y aLowerLimit, Z aUpperLimit)
{
if (integral_less_than(aUpperLimit, aValueToCheck) ||
integral_less_than(aValueToCheck, aLowerLimit))
{
std::cout
<< "ERROR: ValidateParameter():"
<< " aValueToCheck=" << aValueToCheck
<< ", aLowerLimit=" << aLowerLimit
<< ", aUpperLimit=" << aUpperLimit
<< "\n";
// throw(std::out_of_range("Invalid Range"));
}
}
int main()
{
ValidateParameter(0, -1, 1);
ValidateParameter(0u, -1, 1);
ValidateParameter(0, -1, 1u);
ValidateParameter(0u, -1, 1u);
ValidateParameter(-1, -1, 1);
ValidateParameter(-1, -1, 1u);
ValidateParameter(1, -1, 1);
ValidateParameter(1u, -1, 1);
ValidateParameter(1, -1, 1u);
ValidateParameter(1u, -1, 1u);
ValidateParameter(-2, -1, 1);
ValidateParameter(-2, -1, 1u);
ValidateParameter(2, -1, 1);
ValidateParameter(2u, -1, 1);
ValidateParameter(2, -1, 1u);
ValidateParameter(2u, -1, 1u);
return 0;
}
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