用函数替换宏会导致 "signed/unsigned mismatch" 警告

replacing macro with a function causes "signed/unsigned mismatch" warning

对于这个片段

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

即使在高警告级别(所有警告在 Visual Studio 2022 中启用),代码也能完全符合要求。但是,如果我将参数传递给函数

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

编译器生成 '!=': signed/unsigned 不匹配 警告。

如何使函数的行为与“内联”代码相同,即没有警告?


请记住,“真实代码”类似于

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

我想“执行 'right thing'”并用函数替换宏

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

在直接比较中,编译器知道比较双方的类型,因此可以确保 1 是正确大小的无符号类型。

当您调用 lambda 时,rhs 的类型将被推断为 int,因为那才是 1 的真正含义。由于 lhs 将是无符号类型,因此当您将无符号大小与(有符号)int1.

进行比较时,您会在比较中收到警告

编译器必须推导参数的任何类型的可调用对象都会遇到同样的问题,例如函数模板:

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

对于函数模板,解决方案很简单:对两个参数使用相同的单一类型:

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

你的第一个例子是一个具体案例。因此,即使您将有符号 int 与无符号 std::vector::size_type 进行比较,编译器也会告诉您没有问题。它会将 1 隐式转换为无符号类型,表示 1 没有问题。

在第二个例子中,你有一般情况。在为 (size_type, int) 情况实例化 lambda 函数时,必须为任何一组值做好准备。它不能假定 int 的值可以在不改变值的情况下转换为 size_type。 (理论上,它可以内联 lambda,然后它会在这个特定的调用中看到特定的值,但显然它不会那样做。)

解决方案是使两个参数相同(或至少兼容)。

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

ETA: 如果你想做一个专门比较尺寸的功能,你可以在界面中使用特定的类型。

// 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) {}};

然后,当你调用f(v.size(), 1)时,编译器会将int转换为size_t,从而避免比较错误。

如果你想要其他情况的通用性,你可以使用一个函数模板,然后通过明确地对 int 到尺寸类型。

通过调用 std::cmp_not_equal

将问题移出您的代码
const auto f = [](auto&& lhs, auto&& rhs) {
     if (std::cmp_not_equal(lhs, rhs)) { blah blah }
};

在 C++20 中添加的这一系列函数是 defined in a way that properly compares integer arguments even in the presence of a signed/unsigned mismatch

现在标准库作者负责编写“所有那些模板垃圾”,您需要做的只是 #include <utility>


对于断言的特定情况,您无论如何都需要一个宏来捕获信息。

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

不确定它是否完全适用于您的情况,但您可以“强制” 一种操作数类型:

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

std::type_identity 是 C++20,但可以为以前的标准实现。