Макрос теста Google (gtest) `EXPECT_TRUE()` не будет компилироваться с шаблоном `std::is_same<>` в качестве входных данных

В С++ 17 с компилятором clang я получаю те же ошибки сборки, делаю ли я это:

EXPECT_TRUE(std::is_same_v<decltype(var1), decltype(var2)>);

или это:

EXPECT_TRUE(typename std::is_same_v<decltype(var1), decltype(var2)>);,

или это:

EXPECT_TRUE(typename std::is_same_v<typename decltype(var1), typename decltype(var2)>);

Команда сборки:

bazel test //my_target_dir:my_target

Ошибка сборки:

error: too many arguments provided to function-like macro invocation
                decltype(var2)>);
                ^
gtest/gtest.h:1980:9: note: macro 'EXPECT_TRUE' defined here
#define EXPECT_TRUE(condition) \
        ^
myfile.cpp:125:5: error: use of undeclared identifier 'EXPECT_TRUE'
    EXPECT_TRUE(std::is_same_v<
    ^

Обратите внимание, что определение Googletest для EXPECT_TRUE() находится здесь: https://github.com/google/googletest/blob/master/googletest/include/gtest/gtest.h#L1980.

Что не так с тем, что я делаю, и как я могу это скомпилировать?

Использованная литература:

  1. std::is_same<T, U>::value и std::is_same_v<T, U>
  2. Документация GoogleTest (gtest)
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
4 525
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Краткое содержание

Это НЕ работает, потому что препроцессор C++, который обрабатывает макросы, был написан до того, как существовали шаблоны, и видит запятую как разделение двух отдельных аргументов макроса. Он думает, что я вызвал макрос EXPECT_TRUE() с anything<foo в качестве 1-го аргумента и bar> в качестве 2-го аргумента:

// This does NOT work, because the preprocessor sees this 1 template
// argument to the macro as two separate arguments separated by the
// comma
EXPECT_TRUE(anything<foo, bar>);

Эти варианты ДЕЙСТВИТЕЛЬНО работают:

// Option 1: move the template outside of the macro call
bool isSameType = std::is_same_v<decltype(var1), decltype(var2)>;
EXPECT_TRUE(isSameType);

// Option 2: for this particular case I can instead use the 
// `static_assert()` function in place of the `EXPECT_TRUE()` macro
static_assert(std::is_same_v<decltype(var1), decltype(var2)>);

// Option 3: use double parenthesis to force the macro to treat
// the parameter containing comma-separated template parameters
// as a **single argument** to the macro:
EXPECT_TRUE((std::is_same_v<decltype(var1), decltype(var2)>));

Подробности

Проведя некоторое время в чате с друзьями, один из них, Дрю Гросс, объяснил следующее:

Из-за модели препроцессора C++ запятые в экземпляре шаблона интерпретируются как разделяющие аргументы макроса, а не аргументы шаблона. Это одна из многих причин, по которой использование макросов в современном C++ крайне нежелательно. Итак, когда вы пишете:

SOME_MACRO(some_template<a, b>());

это интерпретируется как передача 2 аргументов SOME_MACRO, первый из которых some_template<a, а второй — b>(). Поскольку EXPECT_TRUE принимает только один аргумент, это не скомпилируется.

В данном конкретном случае я бы рекомендовал использовать static_assert вместо EXPECT_TRUE. Если бы то, что вы тестировали, не было постоянной времени компиляции, вам нужно было бы сначала назначить локальную переменную, например.

bool localVar = some_template<a, b>();
EXPECT_TRUE(localVar);

Он точен в правильности. Поскольку препроцессор макросов C и C++ был написан ДО появления C++, он не распознает символы области действия шаблона C++ < и > (он считает, что это просто символы «меньше» и «больше» соответственно), поэтому в этом утверждении (EXPECT_TRUE(std::is_same_v<decltype(var1), decltype(var2)>);), он видит запятую и анализирует std::is_same_v<decltype(var1) как первый аргумент макроса gtest EXPECT_TRUE() и decltype(var2)> как второй аргумент макроса.

Поэтому вот решение:

bool isSameType = std::is_same_v<decltype(var1), decltype(var2)>;
EXPECT_TRUE(isSameType);

Однако, как утверждает Дрю, в этом случае лучшим решением будет просто использовать static_assert() вместо EXPECT_TRUE() gtest, поскольку этот тест может быть выполнен во время компиляции, а не во время выполнения:

(лучшее решение):

static_assert(std::is_same_v<decltype(var1), decltype(var2)>);

Note: message not required for static_assert() in C++17. See here.

Я провел дополнительные исследования и эксперименты, а также обнаружил, что дополнительные скобки тоже решают эту проблему. Использование дополнительных круглых скобок заставляет препроцессор распознавать весь входной аргумент как 1 аргумент макроса, поскольку препроцессор учитывает круглые скобки, но вообще не учитывает символы шаблона <>, поскольку он не поддерживает шаблоны.

Поэтому это тоже работает:

EXPECT_TRUE((std::is_same_v<decltype(var1), decltype(var2)>));

Если вы сомневаетесь, запишите это в скобки. Если нужно, скобки, конечно. :)

Итак, теперь у нас есть 3 жизнеспособных решения этой проблемы. Я бы, вероятно, выбрал вариант static_assert() в качестве основного решения, а вариант с дополнительными скобками чуть выше в качестве решения, если мне нужно было проверить некоторые входные данные шаблона во время выполнения.

Дополнительные ссылки:

Как только я узнал, в чем заключается проблема (препроцессор макросов видит запятую и не распознает операторы области действия шаблона C++ < и >), я смог немного поискать в Google и найти следующие ответы:

  1. Слишком много аргументов предоставлено для функционально-подобного вызова макроса
  2. Получение слишком большого количества аргументов для функционально-подобной ошибки компиляции вызова макроса при определении лямбда внутри assert (assert.h) в Xcode [c++]

Keywords: macro watch out for template parameter inputs; comma argument delimiter to C/C++ macro preprocessor, c++ extra parenthesis required in macros around macro parameters

Другие вопросы по теме