3
votes

I have an annoying compiler warning in my C++ project. It is in a templated function, which checks wether its argument can fit into the range of a particular type, which is mentioned as a template parameter (couldn't find it in standart library, does it have one?).

I am aware of tricky signed to unsigned integral types comparison, thus I specialized this function for differently signed integers, where I firstly check if signed integer is non-negative right before signed to unsiged comparison. However gcc issues a warning for this safe case. I don't want to disable this warning with -Wsign-compare totally, because it is a useful one. The code is listed bellow:

template <typename TRange, typename TSuspect, typename TCommonSignedness>
bool isInRangeOfHelper(const TSuspect & suspect,
                       const TCommonSignedness &,
                       const TCommonSignedness &)
{
    return std::numeric_limits<TRange>::min() <= suspect
         && suspect <= std::numeric_limits<TRange>::max();
}

template <typename TRange, typename TSuspect>
bool isInRangeOfHelper(const TSuspect & suspect, 
                       const std::true_type &, 
                       const std::false_type &)
{
    return suspect <= std::numeric_limits<TRange>::max();
}

template <typename TRange, typename TSuspect>
bool isInRangeOfHelper(const TSuspect & suspect, 
                        const std::false_type &, 
                        const std::true_type &)
{
    return 0 <= suspect 
           && suspect <= std::numeric_limits<TRange>::max();  // WARNING HERE
}


template <typename TRange, typename TSuspect>
bool isInRangeOf(const TSuspect & suspect){
    return isInRangeOfHelper<TRange>(suspect,
                           std::is_signed<TRange>(),
                           std::is_signed<TSuspect>());
}

template <typename TRange>
bool isInRangeOf(const TRange & suspect){ return true; }



int main(int argc, char *argv[]){

    if (!isInRangeOf<unsigned int>(-1)){
        std::cout << "false";
    } else {
        std::cout << "predicate error";
        std::terminate();
    }
    return 0;
}
// warning: comparison of integer expressions of different signedness: 
// ‘const int’ and ‘unsigned int’ [-Wsign-compare]
// return 0 <= suspect && suspect <= std::numeric_limits<TRange>::max();
//                        ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
Use a cast and a comment.n. 1.8e9-where's-my-share m.
@n.m. Use the answer section.Lightness Races in Orbit

3 Answers

5
votes

Once you've verified suspect is nonnegative, you can safely cast it to its type's unsigned version:

template <typename TRange, typename TSuspect>
bool isInRangeOfHelper(const TSuspect & suspect, 
                        const std::false_type &, 
                        const std::true_type &)
{
    return 0 <= suspect 
           && static_cast<std::make_unsigned_t<TSuspect>>(suspect) <= std::numeric_limits<TRange>::max();
}

[Live example]

5
votes

Before the code:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"

After the code:

#pragma GCC diagnostic pop

If you want to expand this from a macro to support non-GCC compilers, then use the equivalent _Pragma() form

Edge cases:

  • In old versions of GCC (4.6? docs don't say), this had to be around the function, but now it can be done at any scope.

  • There may be problems if you try to suppress a warning in a macro, then trigger it in the same macro.

  • Some compiler versions may fail to suppress associated note:s for some diagnostics.

3
votes

There are a few options:

1 UINT_MAX

isInRangeOf<unsigned int>( std::numeric_limits<unsigned_int>::max() );
isInRangeOf<unsigned int>( ​UINT_MAX );
// GCC specific
isInRangeOf<unsigned int>( __UINT32_MAX__ );

2 Bitwise tricks

isInRangeOf<unsigned int>( static_cast<unsigned int>(-1) )
isInRangeOf<unsigned int>( ~0 )
isInRangeOf<unsigned int>( 0xFFFFFFFF )

3 Suppress warnings by pragma directives

(this option can be useful for some unused parameters warnings, when doing some template meta-programming)

#ifdef __GNUG__
# define PUSH_IGNORE_WSIGN_CMP\
          _Pragma("GCC diagnostic push")\
          _Pragma("GCC diagnostic ignored \"-Wsign-compare\" ")
#  define POP_IGNORE_WSIGN_CMP _Pragma("GCC diagnostic pop")
#elif defined(_MSC_VER)
#   define PUSH_IGNORE_WSIGN_CMP
            _Pragma("warning( push )")\
            _Pragma("warning( disable : C4018)")
#   define POP_IGNORE_WSIGN_CMP _Pragma("warning( pop )")
#endif

PUSH_IGNORE_WSIGN_CMP
    if (!isInRangeOf<unsigned int>(-1)){
POP_IGNORE_WSIGN_CMP
        std::cout << "false";