Определите эффективный тип литерала в сравнении - PullRequest
2 голосов
/ 16 марта 2019

Ниже приведена упрощенная версия макроса, который я определил:

#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 или подавляя предупреждение?

Edit: Кажется, есть небольшая путаница вокруг того, что должно и не должно возвращать предупреждение. Использование auto возвращает ненужное предупреждение, когда один из аргументов является положительным литерал , который также является допустимым литералом без знака (но auto приводит к signed). Другими словами, в идеале CHECK_EQ(a, b) будет выдавать предупреждение, если и только если a == b будет. Второе наилучшее решение позволило бы смешивать типы при условии, что в конечном итоге проведенное сравнение является безопасным с.р.т. подпись типов. По-видимому, это достигается с помощью std::common_type.

Ответы [ 2 ]

2 голосов
/ 17 марта 2019

(РЕДАКТИРОВАТЬ есть альтернативное решение в конце)

Решение 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<T> на std::is_signed<T>::value.Если у вас есть C ++ 11 и даже не C ++ 14, замените std::common_type_t<T1, T2> на typename std::common_type<T1, T2>::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
}
0 голосов
/ 16 марта 2019

Может, использовать шаблонную функцию для сравнения?

#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);
}

Предполагая, что T1 и T2 могут статически приводиться друг к другу.

EDIT:

Что касается беспокойства о том, что ~(0U) == -1, если бы он был не желаемым, то, вероятно, нам следует , а не , попытаться вообще отказаться от предупреждения компилятора. Но ~(0U) == -1 не так уж плохо, например Есть довольно много случаев, когда стандартная библиотека использует «-1» для возврата без знака.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...