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

Google test (gtest) `EXPECT_TRUE()` macro won't compile with `std::is_same<>` template as input

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

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<
    ^

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

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

参考文献:

  1. std::is_same<T, U>::value and std::is_same_v<T, U>
  2. GoogleTest (gtest) documentation

总结

这不起作用,因为处理宏的 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>);

这些选项确实有效:

// 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)>));

详情

在与一些朋友聊天后,其中一位 Drew Gross 解释如下:

Due to the C++ preprocessor model, commas within a template instantiation are interpreted as separating arguments of the macro, not arguments of the template. This is one of the many reasons that using macros is heavily discouraged in modern C++. So when you write:

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

it's interpreted as passing 2 arguments to SOME_MACRO, the first being some_template<a, and the second being b>(). Since EXPECT_TRUE only accepts a single argument, this fails to compile.

In this particular case I'd recommend using static_assert instead of EXPECT_TRUE. If the thing you were testing wasn't a compile time constant, you would have to assign to a local variable first, e.g.

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() 而不是 gtest 的 EXPECT_TRUE(),因为这个测试可以在编译时完成而不是 运行-时间:

(更好的解决方案):

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

注意:C++17 中的 static_assert() 不需要消息。 See here.

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

因此,这也有效:

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

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

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

其他参考资料:

一旦我知道问题的本质(宏预处理器看到逗号但不识别 C++ 模板 <> 范围运算符),我就可以进行一些谷歌搜索和找到以下答案也可以看看:

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