(РЕДАКТИРОВАТЬ есть альтернативное решение в конце)
Решение 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
}