Google 测试 (gtest) `EXPECT_TRUE()` 宏不会使用 `std::is_same<>` 模板作为输入进行编译

Gab*_*les 2 c++ macros templates googletest is-same

在使用 clang 编译器的 C++17 中,无论我这样做,我都会遇到相同的构建错误:

EXPECT_TRUE(std::is_same_v<decltype(var1), decltype(var2)>);
Run Code Online (Sandbox Code Playgroud)

或这个:

EXPECT_TRUE(typename std::is_same_v<decltype(var1), decltype(var2)>);,
Run Code Online (Sandbox Code Playgroud)

或这个:

EXPECT_TRUE(typename std::is_same_v<typename decltype(var1), typename decltype(var2)>);
Run Code Online (Sandbox Code Playgroud)

构建命令:

bazel test //my_target_dir:my_target
Run Code Online (Sandbox Code Playgroud)

构建错误:

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<
    ^
Run Code Online (Sandbox Code Playgroud)

请注意,Googletest 的定义EXPECT_TRUE()在这里:https://github.com/google/googletest/blob/master/googletest/include/gtest/gtest.h#L1980

我正在做的事情有什么问题,我怎样才能编译它?

参考:

  1. std::is_same<T, U>::valuestd::is_same_v<T, U>
  2. GoogleTest(gtest)文档

Gab*_*les 7

概括

这不起作用,因为处理宏的 C++ 预处理器是在模板存在之前编写的,并且将逗号视为分隔宏的两个单独参数。它认为我已将EXPECT_TRUE()anything<foo作为第一个参数和bar>第二个参数调用:

// 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>);
Run Code Online (Sandbox Code Playgroud)

这些选项确实有效:

// 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)>));
Run Code Online (Sandbox Code Playgroud)

细节

在与一些朋友聊天一段时间后,其中一位名叫德鲁·格罗斯 (Drew Gross)的朋友解释了以下内容:

由于 C++ 预处理器模型,模板实例化中的逗号被解释为分隔宏的参数,而不是模板的参数。这是在现代 C++ 中强烈反对使用宏的众多原因之一。所以当你写:

SOME_MACRO(some_template<a, b>());
Run Code Online (Sandbox Code Playgroud)

它被解释为将 2 个参数传递给SOME_MACRO,第一个是some_template<a,第二个是b>()。由于EXPECT_TRUE只接受一个参数,因此无法编译。

在这种特殊情况下,我建议使用static_assert而不是EXPECT_TRUE. 如果您要测试的东西不是编译时常量,则必须首先分配给局部变量,例如

bool localVar = some_template<a, b>();
EXPECT_TRUE(localVar);
Run Code Online (Sandbox Code Playgroud)

他说得一点都没错。由于 C 和 C++ 宏预处理器是在 C++ 存在之前编写的,因此它无法识别 C++<>模板作用域符号(它认为它们分别只是“小于”和“大于”符号),因此在此语句中 ( EXPECT_TRUE(std::is_same_v<decltype(var1), decltype(var2)>);) ,它看到逗号并解析std::is_same_v<decltype(var1)为 gtestEXPECT_TRUE()宏的第一个参数,以及decltype(var2)>宏的第二个参数。

因此,解决方案如下:

bool isSameType = std::is_same_v<decltype(var1), decltype(var2)>;
EXPECT_TRUE(isSameType);
Run Code Online (Sandbox Code Playgroud)

然而,正如 Drew 所说,更好的解决方案是在这种情况下使用static_assert()gtest 而不是EXPECT_TRUE(),因为此测试可以在编译时而不是运行时完成:

(更好的解决方案):

static_assert(std::is_same_v<decltype(var1), decltype(var2)>);
Run Code Online (Sandbox Code Playgroud)

static_assert()注意: C++17 中不需要消息。看这里。

我做了一些额外的研究和实验,还发现额外的括号也可以解决这个问题。使用额外的括号会强制预处理器将整个输入参数识别为宏的 1 个参数,因为预处理器尊重括号,但根本不尊重模板<>符号,因为它不支持模板。

因此,这也有效:

EXPECT_TRUE((std::is_same_v<decltype(var1), decltype(var2)>));
Run Code Online (Sandbox Code Playgroud)

如果有疑问,请用括号括起来。如果需要的话,确实需要括号。:)

所以,现在我们有 3 个可行的解决方案来解决这个问题。static_assert()如果我需要在运行时测试一些模板输入,我可能会选择该选项作为我的主要解决方案,并将上面的额外括号选项作为我的解决方案。

附加参考资料:

一旦我知道问题的本质是什么(宏预处理器看到逗号并且不识别 C++ 模板<>作用域运算符),我就能够进行一些谷歌搜索并找到以下答案来查看:

  1. 为类似函数的宏调用提供了太多参数
  2. 在 Xcode 中的断言 (assert.h) 中定义 lambda 时,为类似函数的宏调用提供了太多参数编译错误 [c++]

关键词:宏注意模板参数输入;C/C++ 宏预处理器的逗号参数分隔符,宏参数周围的宏中需要 C++ 额外的括号