В С++ 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.
Что не так с тем, что я делаю, и как я могу это скомпилировать?
Это НЕ работает, потому что препроцессор 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 и найти следующие ответы:
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