Можно ли на этапе компиляции отличить statement от expression?
Вступление
Expression: An expression is a sequence of operators and their operands, that specifies a computation.
(Выражение - это последовательность операторов и их операндов, которая задает вычисление.)
Statement: Statements are fragments of the C++ program that are executed in sequence. The body of any function is a sequence of statements.
(Инструкции - это фрагменты программы на C++, которые выполняются последовательно. Тело любой функции представляет собой последовательность операторов.)
Теперь о главном
Я пишу макрос, который проверяет, что код внутри макроса выбросил какое-то исключение
#define EXPECT_THROW(statement) ...
Этот макрос может принимать выражения или инструкции
// Функция с nodiscard.
[[nodiscard]] int get_int(){
return {};
}
// Функция, которая возвращает void.
void get_void(){
}
struct foo{
foo(int a){
}
};
int main(){
EXPECT_THROW(get_int()); // Проверка, что вызов функции бросает исключение.
EXPECT_THROW(get_void()); // Проверка, что вызов функции (которая ничего не возвращает) бросает исключение.
EXPECT_THROW(foo f = foo(1)); // Проверка, что выражение бросает исключение.
}
Макрос уже написан, но есть одна проблема:
Если я проверяю функцию, которая помечена как [[nodiscard]], то получаю предупреждение
Предупреждение C4834 отмена возвращаемого значения функции с атрибутом "nodiscard"
Я придумал, как избежать предупреждение, но делается это вручную:
// Пусть простейшая реализация для компиляции будет такой
#define EXPECT_THROW(statement) {statement;}
int main(){
// EXPECT_THROW(get_int()); // Было так
// Можно решить проблему 2 способами
EXPECT_THROW(auto const _ = get_int()); // Сохранить значение во временную переменную.
EXPECT_THROW((void)get_int()); // Привести значение к void.
// EXPECT_THROW(get_void()); // Было так
// Тут нет проблемы, но можно применить 2 способа
EXPECT_THROW(get_void()); // Ничего не делать
EXPECT_THROW((void)get_void()); // Привести к void
// Тут нет проблемы и нельзя применить ни один из способов
EXPECT_THROW(foo f = foo(1));
}
Теперь хочется переписать макрос так, чтобы он автоматически приводил тип к void, если statement что-то возвращает.
Для этого я бы определил макрос вот так:
#define EXPECT_THROW(statement) (void)(statement)
Но это не работает для инструкций:
EXPECT_THROW(foo f = foo(1));
EXPECT_THROW(int a = 1);
Получается, что мне нужно отличать этот случай и обрабатывать его без приведения к void, но я не знаю, как это можно сделать.
Отсюда и вопрос: можно ли как-то отличить statement от expression?
Чтобы можно было реализовать макрос как-то так:
#define EXPECT_THROW(statement) \
if constexpr(is_expression(statement)) { \
(void)(statement); \
} else if constexpr(is_statement(statement)) { \
/* nothing */ \
}
Дополнение
В google test есть макрос EXPECT_THROW, который выдает предупреждение на все функции объявленные с nodiscard.
Поэтому я хотел переопределить свой макрос, который бы не выдавал предупреждений
#define MY_EXPECT_THROW(statement) EXPECT_THROW((void)statement)
Но это не работает для некоторых случаев.
Ответы (1 шт):
Как выяснилось, проблема заключалась в том, что макрос EXPECT_THROW из google test выдает предупреждение на все функции объявленные с nodiscard.
Правильным решением в этом случае является отказ от использования макроса EXPECT_THROW и перевод выброшенных исключений в выходные данные теста. Это также полезно, так как обычно надо проверять не просто то, что исключение было выброшено, но еще и его содержимое, что невозможно с EXPECT_THROW. Ну и можно без проблем поставить точку останова в тестируемый код.
::std::size_t runtime_errors_count{};
::std::string err_message{};
try
{
// тут тестируемый код
}
catch (::std::runtime_error const & exception)
{
++runtime_errors_count;
err_message.append(exception.what());
}
EXPECT_EQ(::std::size_t{1} == runtime_errors_count);
EXPECT_EQ(::std::string("total fail") == err_message);