使用googletest的EXPECT_NO_THROW和std :: array编译错误

dat*_*osh 2 c++ googletest c++11 stdarray

尝试在googletest中使用std :: array时出现此错误.以下是产生此错误的最小示例:

arr.cpp

#include "gtest/gtest.h"
#include <array>

TEST(Test, Positive) {
    EXPECT_NO_THROW({
        const std::array<unsigned char, 16> foo = {1, 2, 3};
    });
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
Run Code Online (Sandbox Code Playgroud)

我使用了来自github 的当前googletest代码.制作和安装googletest.

作为编译器,我在Ubuntu 14.04 LTS上使用了clang3.8.

使用follownig命令:

clang++ -std=c++11 -o arr arr.cpp
Run Code Online (Sandbox Code Playgroud)

结果是:

arr.cpp:6:41: error: too many arguments provided to function-like macro invocation
        const std::array<unsigned char, 16> blasdasd = {1, 2, 3};
                                        ^
/usr/local/include/gtest/gtest.h:1845:9: note: macro 'EXPECT_NO_THROW' defined here
#define EXPECT_NO_THROW(statement) \
        ^
arr.cpp:5:5: note: cannot use initializer list at the beginning of a macro argument
    EXPECT_NO_THROW({
    ^               ~
arr.cpp:5:5: error: use of undeclared identifier 'EXPECT_NO_THROW'
    EXPECT_NO_THROW({
    ^
2 errors generated.
Run Code Online (Sandbox Code Playgroud)

删除EXPECT_NO_THROW宏并简单地声明数组编译正常.是否有任何明显的遗漏或我应该在github上提交错误报告?

dyp*_*dyp 6

EXPECT_NO_THROW是一个宏定义如下:

#define EXPECT_NO_THROW(statement) \
  GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_)
Run Code Online (Sandbox Code Playgroud)

如您所见,这是一个类似函数的宏,它接受一个参数.预处理器(处理宏)适用于令牌.它不了解C++,也不了解C,而只了解它自己的令牌语言.(现在,编译和预处理显然发生在一个阶段,但我指的是预处理器语言的语义.)

预处理器需要一个参数EXPECT_NO_THROW.它通过查找逗号分隔类似函数的宏的参数.因此,当它在参数列表中看到类似函数的宏的标记列表时,例如:

EXPECT_NO_THROW( const std::array<unsigned char, 16> foo = {1, 2, 3}; )
Run Code Online (Sandbox Code Playgroud)

然后它将参数列表分成如下参数:

  • const std::array<unsigned char
  • 16> foo = {1
  • 2
  • 3};

这些当然是多个参数,其中一个是类似函数的宏EXPECT_NO_THROW.


为了将多个预处理标记(包括,作为单个参数)传递给类函数宏,可以将这些标记括在括号中:

EXPECT_NO_THROW( (const std::array<unsigned char, 16> foo = {1, 2, 3};) );
Run Code Online (Sandbox Code Playgroud)

但是,这不会编译:

EXPECT_NO_THROW宏如下扩展:

#define GTEST_TEST_NO_THROW_(statement, fail) \
  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
  if (::testing::internal::AlwaysTrue()) { \
    try { \
      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
    } \
    catch (...) { \
      goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \
    } \
  } else \
    GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \
      fail("Expected: " #statement " doesn't throw an exception.\n" \
           "  Actual: it throws.")
Run Code Online (Sandbox Code Playgroud)

无法访问的代码宏定义如下:

#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \
  if (::testing::internal::AlwaysTrue()) { statement; }
Run Code Online (Sandbox Code Playgroud)

所以,如果你STMTEXPECT_NO_THROW宏中放置一个语句,你最终会得到:

  if (::testing::internal::AlwaysTrue()) {
    try {
      if (::testing::internal::AlwaysTrue()) { STMT; };
    }
  // ...
Run Code Online (Sandbox Code Playgroud)

因此,如果你把(STMT;)EXPECT_NO_THROW,你结束了一个线

if (::testing::internal::AlwaysTrue()) { (STMT;); };
Run Code Online (Sandbox Code Playgroud)

该部分(STMT;);不是合法的C++.也不是(STMT);如果这STMT是OP中的声明.

如果你({STMT;})进入宏,你最终将({STMT;});在C++中仍然是非法的,但它在g ++中被允许作为扩展; 这是一个表达式陈述.这里,该{STMT;}部分被解释为一个表达式,括在括号中以形成表达式({STMT;}).

您也可以尝试隔离逗号.正如Yakk在对OP的评论中指出的那样,你可以使用typedef隐藏模板参数列表中的逗号; 初始化列表中的剩余逗号可以使用临时包装,例如:

using array_t = std::array<unsigned char, 16>;
EXPECT_NO_THROW( const array_t foo = (array_t{1, 2, 3}); );
Run Code Online (Sandbox Code Playgroud)

虽然原始版本EXPECT_NO_THROW(STMT)允许STMT作为声明,但C++中的语句不能随意括在括号中.然而,表达式可以任意地括在括号中,并且表达式可以用作语句.这就是为什么将语句作为表达式语句传递的原因.如果我们可以将数组声明表示为表达式,这将解决问题:

EXPECT_NO_THROW(( std::array<unsigned char, 16>{1, 2, 3} ));
Run Code Online (Sandbox Code Playgroud)

注意这会创建一个临时数组; 这不是OP中的声明语句,而是单个表达式.

但是,创建我们想要测试的事物的表达式可能并不总是这么简单.但是,标准C++中有一个表达式可以包含语句:lambda表达式.

EXPECT_NO_THROW(( []{ const std::array<unsigned char, 16> foo = {1, 2, 3}; }() ));
Run Code Online (Sandbox Code Playgroud)

请注意()在lambda之后,这对于在lamdba中实际执行语句很重要!忘记这是一个非常微妙的错误来源:(