feelfree
feelfree

Reputation: 11753

How can I force template parameter type to be signed?

I'll use the following example to illustrate my question:

template<typename T>
T diff(T a, T b)
{
  return a-b;
}

I expect this template function works only when the type T is signed. The only solution I can figure out is to use delete keyword for all the unsigned types:

template<>
unsigned char diff(unsigned char,unsigned char) == delete;
template<>
unsigned char diff(unsigned char,unsigned char) == delete;

Are there other solutions?

Upvotes: 34

Views: 5056

Answers (9)

alfC
alfC

Reputation: 16242

I am surprised nobody answered this, which is pretty robust and reliable and IMO canonical since and after C++11.

template<typename T, class = typename std::enable_if<std::is_signed<T>::value>::type>
T diff(T a, T b)
{
  return a-b;
}

in C++14 you can use:

template<typename T, class = std::enable_if_t<std::is_signed<T>::value>>
T diff(T a, T b)
{
  return a-b;
}

in C++17 you can use:

template<typename T, class = std::enable_if_t<std::is_signed_v<T>>>
T diff(T a, T b)
{
  return a-b;
}

This alternative could help with multiple "overloads" if necessary (I will leave it at there)

template<typename T, std::enable_if_t<std::is_signed_v<T>,int> =0>
T diff(T a, T b)
{
  return a-b;
}

...and in C++20 you can use Concepts (not shown).

Upvotes: 1

HEKTO
HEKTO

Reputation: 4191

There are many good answers here, however this question comes up as a result of the search "c++ template argument signed". We are in 2022 now, so I think it makes sense to add an answer, using concepts in C++20:

#include <concepts>
#include <iostream>

template <std::signed_integral T>
T diff(T a, T b)
{
  return a - b;
}

int main()
{
  std::cout << diff(1U, 2U) << std::endl;
}

The compiler fails to compile the program above with the message (among others):

error: use of function ‘T diff(T, T) [with T = unsigned int]’ with unsatisfied constraints

The core language concept std::signed_integral, used here, is described on this page.

Upvotes: 0

fe263
fe263

Reputation: 107

#include <type_traits>
template<typename T>
std::enable_if_t<(0>-T(1)),T> diff(T a, T b)
{
  return a-b;
}

use (0>-T(1)) ,I assume init T for -1 will less than 0. and unsigned value not possible less than 0

Upvotes: -1

Micha&#235;l Roy
Micha&#235;l Roy

Reputation: 6471

What does your program expect as a result? As it stands, you return an unsigned as a result of a difference. IMHO, this is a bug waiting to happen.

#include <type_trait>

template<typename T>
auto diff(T&& a, T&& b)
{
    static_assert (std::is_unsigned<T>::value);
    return typename std::make_signed<T>::type(a - b);
}

A more modern wait to write this:

inline auto diff(const auto a, const auto b)
{
    static_assert (   std::is_unsigned<decltype(a)>::value 
                   && std::is_unsigned<decltype(b)>::value );
    return typename std::make_signed<decltype(a -b)>::type(a - b);
}

[edit] I feel the need to add this comment: using unsigned integral types in math equations is always tricky. The example above would be a very useful add-on to any math package, if real-life situations, you often have to resort to casting to make the result of differences signed, or the math doesn't work.

Upvotes: 2

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

So there are a few issues I have with your function.

First, your function requires all 3 types to match -- the left, right and result types. So signed char a; int b; diff(a-b); won't work for no good reason.

template<class L, class R>
auto diff( L l, R r )
-> typename std::enable_if<
  std::is_signed<L>::value && std::is_signed<R>::value,
  typename std::decay<decltype( l-r )>::type
>::type
{
  return l-r;
}

the second thing I'd want to do is make a diff object; you cannot easily pass your diff function around, and higher order functions are awesome.

struct diff_t {
  template<class L, class R>
  auto operator()(L l, R r)const
  -> decltype( diff(l,r) )
  { return diff(l,r); }
};

Now we can pass diff_t{} to an algorithm, as it holds the "overload set" of diff in one (trivial) C++ object.

Now this is serious overkill. A simple static_assert can also work.

The static_assert will generate better error messages, but won't support other code using SFINAE to see if diff can be called. It will simply generate a hard error.

Upvotes: 2

Louen
Louen

Reputation: 3677

How about static assert with std::is_signed ?

template<typename T>
T diff(T a, T b)
{
    static_assert(std::is_signed<T>::value, "signed values only");
    return a-b;
}

See it live there : http://ideone.com/l8nWYQ

Upvotes: 36

mister why
mister why

Reputation: 1987

I would use static_assert with a nice error message. enable_if will only get your IDE in trouble and fail to compile with a message like

identifier diff not found

which doesn't help much.

So why not like this:

#include <type_traits>

template <typename T>
T diff(T a, T b)
{
    static_assert(std::is_signed< T >::value, "T should be signed");
    return a - b;
}

that way, when you invoke diff with something else than a signed type, you will get the compiler to write this kind of message:

error: T should be signed

with the location and the values to the call to diff and that's exactly what you're looking for.

Upvotes: 11

alexeykuzmin0
alexeykuzmin0

Reputation: 6440

You can use std::is_signed together with std::enable_if:

template<typename T>
T diff(T a, T b);

template<typename T>
std::enable_if_t<std::is_signed<T>::value, T> diff(T a, T b) {
    return a - b;
}

Here std::is_signed<T>::value is true if and only if T is signed (BTW, it is also true for floating-point types, if you don't need it, consider combining with std::is_integral).

std::enable_if_t<Test, Type> is the same as std::enable_if<Test, Type>::type. std::enable_if<Test, Type> is defined as an empty struct in case Test is false and as a struct with an only typedef type equal to template parameter Type otherwise.

So, for signed types, std::enable_if_t<std::is_signed<T>::value, T> is equal to T, while for unsigned it's not defined and compiler uses SFINAE rule, so, if you need to specify an implementation for a particular non-signed type, you can easily do that:

template<>
unsigned diff(unsigned, unsigned)
{
    return 0u;
}

Some relevant links: enable_if, is_signed.

Upvotes: 45

Edgar Rokjān
Edgar Rokjān

Reputation: 17483

As another option, you might probably add static_assert with std::is_signed type trait:

template<typename T>
auto diff(T x, T y)
{
    static_assert(std::is_signed<T>::value, "Does not work for unsigned");
    return x - y;
}

So that:

auto x = diff(4, 2); // works
auto x = diff(4U, 2U); // does not work

Upvotes: 7

Related Questions