#c #templates #macros #compiler-warnings
#c #шаблоны #макросы #предупреждения компилятора
Вопрос:
Ниже приведена упрощенная версия макроса, который я определил:
#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 amp; b_eval */
abort();
}
} while (false)
Я подозреваю, что это тоже не совсем правильно, поскольку decltype(1) будет int . Есть ли какой-либо способ выполнить то, что я хотел бы, без изменения существующих CHECK_EQ
вызовов или подавления предупреждения?
Редактировать: Кажется, есть небольшая путаница в том, что должно и не должно возвращать предупреждение. Использование auto
возвращает ненужное предупреждение, когда один из аргументов является положительным литералом, который также является допустимым литералом без знака (но auto
приводит к signed
). Другими словами, в идеале CHECK_EQ(a, b)
было бы выдавать предупреждение тогда и только тогда, a == b
если бы. Вторым лучшим решением было бы разрешить смешивание типов при условии, что в конечном итоге выполняемое сравнение безопасно с точки зрения подписи типов. Похоже, это достигается с помощью std::common_type
.
Комментарии:
1. Вы ищете
std::common_type
? Или, может бытьdecltype( a b )
?2. Предупреждение кажется мне действительным в этой ситуации (то есть это предупреждение о законной потенциальной проблеме с вызывающим кодом). Вы должны использовать соответствующее приведение, чтобы гарантировать сопоставимость выражений
a
иb
при вызове макроса, а не внутри самого макроса.
Ответ №1:
(РЕДАКТИРОВАТЬ в конце есть альтернативное решение)
Решение 1 (оригинальное)
Это никогда не работало корректно и будет неверным как с CommonType, так и с std:::common_type
. Это было и будет неверно, поскольку ~(0U) != -1
вычисляется как false в такой схеме (предполагающей дополнение 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 T1amp; a, const T2amp;b)
{
if (std::is_signed_v<T1> amp;amp; !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> amp;amp; 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 amp; b_eval */
abort();
}
} while (false)
Но если уж мы на этом остановились, почему бы просто не использовать шаблонные функции полностью?
template <typename T, typename U>
void check_eq(const Tamp; a, const Uamp; 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
where a == 1
не будет предупреждать, если a
значение без знака.
Для этого я представляю макрос IS_INT_LITERAL:
template <std::size_t N>
constexpr bool is_int_str(const char (amp;str)[N])
{
// TODO: deal with 0x1Dbef hex literals
if (N < 2 || str[N-1] != '') 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 amp;amp; str[i] != '1' ...)
if (str[i] < '0' || str[i] > '9') {
if (i == 0) return false;
// support 2ull , 1L, etc.
if (str[i] !='U' amp;amp;
str[i] != 'L' amp;amp;
str[i] != 'u' amp;amp;
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 amp; a, const T2 amp; 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
}
Комментарии:
1. Извините, откуда берется ~ (0u) и почему вы говорите, что я ожидаю, что он будет отличаться от -1? Кроме того, я не был знаком с common_type , я думаю, это все, что мне нужно; какова цель выполнения проверок is_signed вместо того, чтобы просто переходить к части common_type_t?
2. @Luis вопрос имеет
CHECK_EQ(some_unsigned, 1)
. 0u — это значение без знака, равное 0. Таким образом,~(0u)
это максимальное значение без знака. Значение без знака является положительным и математически отличается от всех отрицательных значений, таких как-1
. Компилятор предупреждает, что язык производит сравнение, которое математически неверно, и это то, чего я пытаюсь избежать в ответе. Ответ пытается поддерживать математическую корректность, как это автоматически делают другие языки.3. @Luis Я обновил ответ в соответствии с вашей первой альтернативой — по-другому обрабатываю литералы int и подавляю предупреждения только для литералов
4. Спасибо @Michael, я думаю, что выполнить точный эквивалент буквального
a == b
выражения не так просто
Ответ №2:
Может быть, использовать шаблонную функцию для сравнения?
#include <iostream>
template<typename T1, typename T2>
static inline bool _NotEqual(const T1amp; a, const T2amp; 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
могут статически приводить друг к другу.
Редактировать:
Что касается опасения, что ~(0U) == -1
если бы это было нежелательно, то нам, вероятно, не следовало пытаться отменить предупреждение компилятора в первую очередь. Но ~(0U) == -1
на самом деле это не так уж плохо, например, существует довольно много случаев, когда стандартная библиотека использует «-1» для возврата без знака.
Комментарии:
1. Зачем продолжать использовать макрос? По мере того, как макрос становится длиннее, целесообразность использования макроса уменьшается.
2. Я просто следую примеру оригинального poster, пытаясь внести минимальные изменения в его исходный код.