Consider this piece of code:
template <typename T>
T abs(const T& n)
{
if (!std::is_signed<T>::value)
throw logic_error;
if (n < 0)
return -n;
return n;
}
I want to completely forbid the usage of my function with unsigned
variables, since it doesn't make sense, and probably the user doesn't even know that he uses an unsigned
variable. For example, I can somewhat avoid this problem:
template <typename T>
T abs(const T& n)
{
if constexpr(!std::is_signed<T>::value)
n = "";
if (n < 0)
return -n;
return n;
}
But if I call abs(4u)
, the compiler errors are not very obvious. Something like "can't apply = const char[1] to double"
. Can I make it somehow more obvious? Or just make multiple overloads?
CodePudding user response:
#include <type_traits>
template <typename T>
T abs(T n)
{
static_assert(std::is_signed<T>::value, "Type must be signed.");
if (n < 0) { return -n; }
return n;
}
int main()
{
return abs(3u);
}
CodePudding user response:
1. concepts
Since C 20 you can use concepts for this.
If you are fine with integer only parameters you could use std::signed_integral
:
#include <concepts>
template <std::signed_integral T>
T abs(const T& n)
{
if (n < 0)
return -n;
return n;
}
If you want to also allow double
, etc... you'd have to make your own concept:
template<class T>
concept signed_value = std::is_signed_v<T>;
template <signed_value T>
T abs(const T& n)
{
if (n < 0)
return -n;
return n;
}
This would result in a compiler error like this if you try to use an unsigned type:
error: no matching function for call to 'abs(unsigned int&)'
note: candidate: 'template<class T> requires signed_integral<T> T abs(const T&)'
Which makes it very clear that abs()
only accepts signed types from just the signature.
2. SFINAE
If C 20 is not available, you could use SFINAE, although the error messages you would get are quite cryptic:
template <class T>
std::enable_if_t<std::is_signed_v<T>, T> abs(const T& n)
{
std::cout << "HELLO" << std::endl;
if (n < 0)
return -n;
return n;
}
But this would result in an error message like this:
error: no matching function for call to 'abs(unsigned int&)'
note: candidate: 'template<class T> std::enable_if_t<is_signed_v<T>, T> abs(const T&)'
note: template argument deduction/substitution failed:
In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = unsigned int]':
required by substitution of 'template<class T> std::enable_if_t<is_signed_v<T>, T> abs2(const T&) [with T = unsigned int]'
So, you need to be comfortable with SFINAE errors for this one.
3. good old static_assert
Alternatively, you could also just add a static_assert
to your function.
This won't be visible in the declaration at all though, only in the definition of the function.
template <class T>
T abs(const T& n)
{
static_assert(std::is_signed_v<T>, "T must be signed!");
if (n < 0)
return -n;
return n;
}
4. using std::abs()
C already provides a std::abs()
implementation that handles all the potential edge cases for you, so if you can, I would recommend using that one instead.
Your code also contains a potential bug if you pass in the minimum value for a given int type, e.g. INT_MIN
.
- The smallest value a 32bit int can represent is -2147483648
- But the largest it can represent is only 2147483647
So, a call to your abs()
function with the minimal value, e.g. abs(-2147483648)
would result in undefined behaviour (std::abs()
has the same behaviour, but it's still worth pointing out).