23

Given:

template<typename T>
inline bool f( T n ) {
  return n >= 0 && n <= 100;
}   

When used with an unsigned type generates a warning:

unsigned n;
f( n ); // warning: comparison n >= 0 is always true

Is there any clever way not to do the comparison n >= 0 when T is an unsigned type? I tried adding a partial template specialization:

template<typename T>
inline bool f( unsigned T n ) {
  return n <= 100;
}   

but gcc 4.2.1 doesn't like that. (I didn't think that kind of partial template specialization would be legal anyway.)

Paul J. Lucas
  • 6,895
  • 6
  • 44
  • 88
  • 3
    Note that there is no partial specialization for function templates, only full specialization. That said, full specialization is usually a bad idea for function templates because the rules regarding what gets specialized, what gets overloaded, and how overload resolution decides what to use are complicated and convoluted. Thankfully, overloading and SFINAE (substitution failure is not an error) are sufficient here. – James McNellis Jan 21 '11 at 18:12
  • I get no warning from Clang 3.8 (or GCC 8.0) on this. If I remove the template on `f` I get it though. Is there a version of `-Wtautological-compare` which considers template instantiations? – user2023370 Feb 01 '18 at 17:42

5 Answers5

26

You can use enable_if with the is_unsigned type trait:

template <typename T>
typename std::enable_if<std::is_unsigned<T>::value, bool>::type f(T n)
{
    return n <= 100;  
}

template <typename T>
typename std::enable_if<!std::is_unsigned<T>::value, bool>::type f(T n)
{
    return n >= 0 && n <= 100;  
}

You can find enable_if and is_unsigned in the std or std::tr1 namespaces if your compiler supports C++0x or TR1, respectively. Otherwise, Boost has an implementation of the type traits library, Boost.TypeTraits. The boost implementation of enable_if is a little different; boost::enable_if_c is similar to the TR1 and C++0x enable_if.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 1
    Why does it work only with the return type, and not with the argument (which it is about, and would find it more logical), like: `template bool (typename std::enable_if::value, T>::type n)` – jaques-sam Feb 05 '19 at 12:44
16

You can take advantage of the wrap-around behavior of unsigned integers.

template<bool> struct bool_ { };

template<typename T>
inline bool f( T n, bool_<false> ) {
  return n >= 0 && n <= 100;
}

template<typename T>
inline bool f( T n, bool_<true> ) {
  return n <= 100;
}

template<typename T>
inline bool f( T n ) {
  return f(n, bool_<(static_cast<T>(-1) > 0)>());
}   

It's important not to say >= 0, to avoid a warning again. The following appears to trick GCC too

template<typename T>
inline bool f( T n ) {
  return (n == 0 || n > 0) && n <= 100;
}   
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
9

Starting in with the introduction of if constexpr you don't even need to provide specializations for this. Unlike a normal if statement the code in the if constexpr will be discarded (not compiled) if the expression is not true. That means you can rewrite your function like

template<typename T>
inline bool f( T n ) 
{
    if constexpr (std::is_unsigned_v<T>)
        return n <= 100;
    else
        return n >= 0 && n <= 100;
}   
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
1

Is there any clever way not to do the comparison n >= 0 when T is an unsigned type? I tried adding a partial template specialization:

The optimizer should drop the code for the compare since it detected the condition.

For Clang, add -Wno-tautological-compare to squash the warning. For GCC/G++, add -Wno-type-limits to squash the warning.

If you are using a compiler that support pragma diagnostic {push|pop} you can:

#if (GCC_VERSION >= 40600) || (LLVM_CLANG_VERSION >= 10700) || (APPLE_CLANG_VERSION >= 20000)
# define GCC_DIAGNOSTIC_AVAILABLE 1
#endif    

#if MSC_VERSION
# pragma warning(push)
# pragma warning(disable: 4389)
#endif

#if GCC_DIAGNOSTIC_AVAILABLE
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsign-compare"
# if (LLVM_CLANG_VERSION >= 20800) || (APPLE_CLANG_VERSION >= 30000)
#  pragma GCC diagnostic ignored "-Wtautological-compare"
# elif (GCC_VERSION >= 40300)
#  pragma GCC diagnostic ignored "-Wtype-limits"
# endif
#endif

template<typename T>
inline bool f( T n ) {
  return n >= 0 && n <= 100;
}

#if GCC_DIAGNOSTIC_AVAILABLE
# pragma GCC diagnostic pop
#endif

#if MSC_VERSION
# pragma warning(pop)
#endif

Also see Comparison is always false due to limited range…

jww
  • 97,681
  • 90
  • 411
  • 885
0

You can implement a special template function implementation for unsigned type like:

template<class T> bool f(T val);
template<> bool f<unsigned>(unsigned val);

UPDATE Unsigned flag

You can implement different implementations for all unsigned types you'd like to use or add a bool flag like:

template <class T, bool U> bool f(T val)
{
        if (U)
                return val <= 100;
        else
                return (val >=0 ) && (val <= 100);
}

...

cout << f<int, false>(1) << endl;
cout << f<int, false>(-1) << endl;
cout << f<char, true>(10) << endl;
Elalfer
  • 5,312
  • 20
  • 25
  • Except it has to work for all unsigned types: char, short, int, long. – Paul J. Lucas Jan 21 '11 at 18:03
  • Your solution is a lot more verbose that the others that take advantage of "enable_if" (or similar functionality). – Paul J. Lucas Jan 21 '11 at 18:29
  • 1
    Your solution also seems to be the only one that can be implemented in C++03, without using boost. That is a clear plus in my book. I'll upvote when I get it to work ;) – Paul Sep 10 '14 at 08:40