【问题标题】:Determine the effective type of a literal in a comparison在比较中确定文字的有效类型
【发布时间】:2019-03-16 20:18:39
【问题描述】:

以下是我定义的宏的简化版本:

#define CHECK_EQ(a, b) do { if ((a) != (b)) abort(); } while (false)

这可行,但现在我想对评估的 a/b 值进行额外的工作,并且只想评估每个值一次。换句话说,类似于:

#define CHECK_EQ(a, b)          \
  do {                          \
    const auto a_eval = (a);    \
    const auto b_eval = (b);    \
    if (a_eval != b_eval) {     \
      /* Print a_eval/b_eval */ \
      abort();                  \
    }                           \
  } while (false)

但这打破了一些当前的用途,例如触发-Wsign-compare CHECK_EQ(some_unsigned, 1)。我想要的是而不是auto 来确定比较的每一侧将转换为用于比较的类型。假设示例:

#define CHECK_EQ(a, b)                                           \
  do {                                                           \
    using CmpType = CommonType<decltype(a), decltype(b)>::type;  \ What goes here??
    const CmpType a_eval = (a);                                  \
    const CmpType b_eval = (b);                                  \
    if (a_eval != b_eval) {                                      \
      /* Print a_eval & b_eval */                                \
      abort();                                                   \
    }                                                            \
  } while (false)

我怀疑这也不完全正确,因为 decltype(1) 将是 int。有什么方法可以在不修改现有 CHECK_EQ 调用或禁止警告的情况下完成我想要的操作?

编辑: 关于应该和不应该返回警告的内容似乎有些混乱。当参数之一是正的 literal 时,使用 auto 会不必要地返回警告,这也是一个有效的无符号文本(但 auto 会导致 signed)。换句话说,理想情况下CHECK_EQ(a, b) 会产生警告当且仅当a == b 会。第二个最佳解决方案将允许混合类型,只要最终执行的比较是安全的 w.r.t.类型的签名。这似乎是使用std::common_type 完成的。

【问题讨论】:

  • 您在寻找std::common_type 吗?或者decltype( a + b )
  • 在这种情况下,警告对我来说似乎是有效的(也就是说,它警告调用代码存在合法的潜在问题)。您应该使用适当的转换来确保 ab 表达式在调用宏时是可比较的,而不是在宏本身内。

标签: c++ templates macros compiler-warnings


【解决方案1】:

(编辑最后有一个替代解决方案)

解决方案 1(原始)

这从来没有正常工作过,并且对于 CommonType 和 std:::common_type 都将不正确。因为~(0U) != -1 在这样的方案中评估为假(假设 2 的补码),所以它过去和将来都是不正确的,您似乎期望它返回 true

我建议使用模板函数:

// check if this is a simple int literal 
// such as 1, 0, 6789, but not 1U and neither expressions like -1.
template <class T1, class T2>
bool is_same(const T1& a, const T2&b)
{
   if (std::is_signed_v<T1> && !std::is_signed_v<T2>) {
       // some compilers might warn about the following,
       // in that case make it an "if constexpr" instead.
       if (a < 0) return false;
   }
   if (!std::is_signed_v<T1> && std::is_signed_v<T2>) {
       if (b < 0) return false;
   }
   std::common_type_t<T1, T2> a_common = a;
   std::common_type_t<T1, T2> b_common = b;
   return a == b;
}

然后你可以写:

#define CHECK_EQ(a, b)                   \
  do {                                   \
    if (!is_same(a_eval, b_eval)) {      \
      /* Print a_eval & b_eval */        \
      abort();                           \
    }                                    \
  } while (false)

但如果我们这样做了,为什么不一直使用模板函数呢?

template <typename T, typename U>
void check_eq(const T& a, const U& b)
{
   if (!is_same(a,b))
   {
       /* print a and b */
       abort();
   }
}

注意:如果您使用的是 C++14 而不是 C++17,则将 std::is_signed_v&lt;T&gt; 替换为 std::is_signed&lt;T&gt;::value。如果你有 C++11 甚至没有 C++14,那么将 std::common_type_t&lt;T1, T2&gt; 替换为 typename std::common_type&lt;T1, T2&gt;::type


解决方案 2

在对问题进行编辑后,文字 int 与任何其他类型的 int 值之间似乎存在区别。代码应该给出与a == b 相同的警告,其中a == 1 不会在a 未签名时发出警告。

为此我介绍宏 IS_INT_LITERAL:

template <std::size_t N>
constexpr bool is_int_str(const char (&str)[N])
{
    // TODO: deal with 0x1Dbef hex literals
    if (N < 2 || str[N-1] != '\0') return false;
    for (unsigned i=0 ; i < N-1 ; ++i)
        // NOTE: This is only 99.9% portable. It assumes that '0'..'9' chars are consecutive.
        //A more portable way would check (str[i] != '0 && str[i] != '1' ...)
        if (str[i] < '0' || str[i] > '9') {
            if (i == 0) return false;
            // support 2ull , 1L, etc.
            if (str[i] !='U' && 
                 str[i] != 'L' &&
                 str[i] != 'u' &&     
                 str[i] != 'l' ) /* lower case L*/
            {
                return false;
            }
        }
    return true;
}
#define IS_INT_LITERAL(x) is_int_str(#x)

然后可以在比较函数中使用该宏:

template <bool suppress_sign_warnings, class T1, class T2>
bool is_same(const T1 & a, const T2 & b)
{
    if constexpr (suppress_sign_warnings) {
        std::common_type_t<T1, T2> a_common = a, b_common = b;
        return a_common == b_common;
    } else {
        return a == b;
    }
}

#define CHECK_EQ(a, b)          \
  do {                          \
    const auto a_eval = (a);    \
    const auto b_eval = (b);    \
    constexpr bool any_literal = IS_INT_LITERAL(a) || IS_INT_LITERAL(b); \
    if (! is_same<any_literal>(a_eval, b_eval)) {     \
      /* Print a_eval/b_eval */ \
      abort();                  \
    }                           \
  } while (false)

这可以在没有警告的情况下工作:

CHECK_EQ(1, 1u); // like 1 == 1u

但这会产生警告:

void foo(int a, unsigned b = 1u)
{
   CHECK_EQ(a, b); // like a == b
}

【讨论】:

  • 对不起,~(0u) 是从哪里来的,为什么你说我希望它不同于-1?另外,我对 common_type 不熟悉,我想这就是我所需要的;进行 is_signed 检查而不是跳到 common_type_t 部分的目的是什么?
  • @Luis 问题有CHECK_EQ(some_unsigned, 1)。 0u 是无符号值 0。所以 ~(0u) 是最大无符号值。无符号值是正的,并且在数学上不同于所有负值,例如-1。编译器认为该语言进行了数学上不正确的比较,这就是我在答案中试图避免的。答案试图维护数学的正确性,就像其他语言自动做的那样。
  • @Luis 我已经根据您的第一个替代方案更新了答案 - 以不同的方式处理 int 文字,并仅针对文字禁止警告
  • 谢谢@Michael,我猜想完全等同于文字a == b 表达式并不那么容易
【解决方案2】:

也许使用模板函数进行比较?

#include <iostream>

template<typename T1, typename T2>
static inline bool _NotEqual(const T1& a, const T2& b)
{
  if (static_cast<T2>(static_cast<T1>(b)) == b) {
    return a != static_cast<T1>(b);
  } else {
    return static_cast<T2>(a) != b;
  }
}

#define CHECK_EQ(a, b)                                          \
  do {                                                          \
    const auto a_eval = (a);                                    \
    const auto b_eval = (b);                                    \
    if (_NotEqual(a_eval, b_eval)) {                            \
      std::cerr << a_eval <<" != "<< b_eval << std::endl;       \
      abort();                                                  \
    }                                                           \
  } while (false)

int main()
{
  CHECK_EQ(1U, 1);
  CHECK_EQ(2, 2.2);
}

假设T1T2 可以相互静态转换。

编辑:

关于~(0U) == -1 的担忧,如果需要,那么我们可能应该首先尝试丢弃编译器警告。但是~(0U) == -1 并不是一件坏事,例如在很多情况下,标准库使用“-1”作为无符号返回。

【讨论】:

  • 为什么要继续使用宏?随着宏变长,宏使用的适当性变小。
  • 我只是按照原始海报的示例,尝试对他的原始代码进行最小的更改。
猜你喜欢
  • 2012-09-17
  • 1970-01-01
  • 1970-01-01
  • 2019-07-09
  • 2020-10-12
  • 1970-01-01
  • 2017-02-10
  • 2018-01-21
  • 1970-01-01
相关资源
最近更新 更多