Home > Software design >  replacing macro with a function causes "signed/unsigned mismatch" warning
replacing macro with a function causes "signed/unsigned mismatch" warning

Time:05-04

For this snippet

   using T = int;
   const std::vector<T> v;
   if (v.size() != 1) {} // could call operator!=()

the code complies cleanly even at high warnings levels (all warnings enabled in Visual Studio 2022). However, if I pass the arguments to a function

   const auto f = [](auto&& lhs, auto&& rhs) { if (lhs != rhs) {}};
   f(v.size(), 1);

the compiler generates a '!=': signed/unsigned mismatch warning.

How can I make the function behave the same way as the "inline" code, i.e., no warning?


Keep in mind that the "real code" is something like

#define f(lhs, rhs) if (lhs != rhs) {}

I'd like to "do the 'right thing'" and replace the macro with a function

temmplate<typename TLhs, typename TRhs>
inline void f(TLhs&& lhs, TRhs&& rhs)
{
   if (lhs != rhs) {}
}

CodePudding user response:

In the direct comparison the compiler knows the type of both side of the comparison, and can therefore make sure that the 1 will be an unsigned type of the correct size.

When you call your lambda the type for rhs will be deduced as an int, because that's what 1 really is. And since lhs will be an unsigned type, you get the warning in the comparison when you compare the unsigned size with the (signed) int value 1.


You will get the same problem with any kind of callable object where the compiler must deduce the arguments, like for example function templates:

template<typename T, typename U>
void f(T const& lhs, U const& rhs);

For a function template the solution is simple: Use the same single type for both arguments:

template<typename T>
void f(T const& lhs, T const& rhs);

CodePudding user response:

Move the problem out of your code by calling std::cmp_not_equal

const auto f = [](auto&& lhs, auto&& rhs) {
     if (std::cmp_not_equal(lhs, rhs)) { blah blah }
};

This family of functions, added in C 20, is defined in a way that properly compares integer arguments even in the presence of a signed/unsigned mismatch.

Now the Standard library author is responsible for writing "all that template gunk" and all you need to do extra is #include <utility>


For the specific case of assertions, you need a macro anyway to capture information.

#define ASSERT_EQ(actual, expected) do { \
      if (lhs == rhs) break; \
      log_assertion_failure(__FILE__, __LINE__, #actual, actual, expected); \
  } while(0)

CodePudding user response:

Your first example is a specific case. So even though you're comparing a signed int to an unsigned std::vector::size_type, the compiler tell that there's no problem. It will implicitly convert 1 to the unsigned type, which has no problem representing a 1.

In the second example, you have the general case. When instantiating the lambda function for the (size_type, int) case, it has to be prepared for any set of values. It cannot assume that the value of the int can be converted to the size_type without altering the value. (In theory, it could inline the lambda, and then it would see the specific values in this particular invocation, but apparently it doesn't do that.)

The solution is to make the two parameters identical (or, at least, compatible).

f(v.size(), 1u);

ETA: If you want to make the function specifically for comparing sizes, you could use specific types in the interface.

// Assuming `std::vector::size_type` is the same as `std::size_t`...
const auto f = [](std::size_t lhs, std::size_t rhs) { if (lhs != rhs) {}};

Then, when you call f(v.size(), 1), the compiler will convert the int to size_t, which will avoid the comparison error.

If you want the generality for other cases, you could use a function template, and then specialize it for the (size_t, int) case by having it explicitly case the int to the size type.

CodePudding user response:

Not sure if it applies to your case fully, but you can "force" type of one operand:

temmplate <typename T>
void f(const T& lhs, const std::struct_identity_t<T>& rhs)
{
   if (lhs != rhs) {/*..*/}
}

std::type_identity is C 20, but can be implemented for previous standard.

  • Related