Ошибка компиляции с использованием EXPECT_NO_THROW от googletest с массивом std::

Я получил эту ошибку при попытке работать с std:: array в googletest. Ниже приведен минимальный пример этой ошибки:

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();
}

Я использовал текущий код googletest от github. Сделать и установить Googletest.

В качестве компилятора я использовал clang3.8 на Ubuntu 14.04 LTS.

Используя команду follownig:

clang++ -std=c++11 -o arr arr.cpp

Результаты в:

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.

Удаление макроса EXPECT_NO_THROW и простое объявление массива прекрасно компилируется. Есть ли что-то очевидное, чего мне не хватает, или я должен сообщить об ошибке на github?

2 ответа

Решение

EXPECT_NO_THROW макрос определяется следующим образом:

#define EXPECT_NO_THROW(statement) \
  GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_)

Как видите, это функциональный макрос, который принимает один аргумент. Препроцессор (который работает с макросами) работает с токенами. Он не понимает ни C++, ни C, а только собственный токен. (В настоящее время, по-видимому, компиляция и предварительная обработка происходят в один этап, но я имею в виду семантику языка препроцессора.)

Препроцессор ожидает один аргумент для EXPECT_NO_THROW, Он разделяет аргументы для функционально-подобных макросов путем поиска запятых. Поэтому, когда он видит список токенов в списке аргументов для подобного функции макроса, такого как:

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

затем он разделяет список аргументов на аргументы следующим образом:

  • 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};) );

Однако, это не скомпилирует:

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.")

Где макрос недоступного кода определяется следующим образом:

#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \
  if (::testing::internal::AlwaysTrue()) { statement; }

Итак, если вы поставите заявление STMT внутри EXPECT_NO_THROW макрос, в итоге вы получите:

  if (::testing::internal::AlwaysTrue()) {
    try {
      if (::testing::internal::AlwaysTrue()) { STMT; };
    }
  // ...

Поэтому, если вы положите (STMT;) в EXPECT_NO_THROW, вы в конечном итоге с линией

if (::testing::internal::AlwaysTrue()) { (STMT;); };

Часть (STMT;); не является законным C++. Ни то, ни другое (STMT); если это STMT это декларация как в ОП.

Если вы пройдете ({STMT;}) в макрос, вы в конечном итоге ({STMT;}); что все еще запрещено в C++, но разрешено в g++ как расширение; это выражение-выражение. Здесь {STMT;} часть интерпретируется как выражение, заключенное в скобки для формирования выражения ({STMT;}),

Вы также можете попытаться выделить запятые. Как указал Yakk - Adam Nevraumont в комментарии к OP, вы можете скрыть запятую в списке аргументов шаблона, используя typedef; оставшиеся запятые в списке инициализаторов могут быть перенесены с помощью временного, например:

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

Пока оригинал EXPECT_NO_THROW(STMT) позволяет STMT чтобы быть оператором, операторы в C++ не могут быть произвольно заключены в скобки. Выражения, однако, могут быть произвольно заключены в круглые скобки, и выражения могут быть использованы как оператор. Вот почему передача выражения как выражения-выражения работает. Если мы сможем сформулировать наше объявление массива как выражение, это решит проблему:

EXPECT_NO_THROW(( std::array<unsigned char, 16>{1, 2, 3} ));

Обратите внимание, что это создает временный массив; это не оператор объявления, как в OP, а отдельное выражение.

Но не всегда бывает так просто создать выражение того, что мы хотим проверить. Однако в стандарте C++ есть одно выражение, которое может содержать операторы: лямбда-выражение.

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

Пожалуйста, обратите внимание () после лямбды, которая важна для того, чтобы фактически выполнить инструкцию внутри лямбды! Забыть это очень тонкий источник ошибок:(

Так что, как многие из вас отметили в комментариях, макросы и шаблоны плохо работают вместе. Задокументировано ли это ограничение googletest в документации? Я не могу найти ничего, намекающего на такое ограничение.

Если у вас есть другие обходные пути, пожалуйста, предоставьте их, и я добавлю их в свой ответ, если у вас есть реальный ответ, пожалуйста, поделитесь:)

Далее я попробовал предложенные решения и скомпилировал их, используя:

clang++ -std=c++11 -o arr arr.cpp -lgtest_main -lgtest -lpthread

Дополнительные скобки вокруг аргумента EXPECT_NO_THROW работают:

#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();
}

Предоставить объявление об использовании не работает:

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

using uchar_16_arr = std::array<unsigned char, 16>;

TEST(Test, Positive) {
    EXPECT_NO_THROW({
        const uchar_16_arr foo = {1, 2, 3};
    });
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Как это все еще приводит к тому же сообщению об ошибке:

arr.cpp:8:38: error: too many arguments provided to function-like macro invocation
        const uchar_16_arr foo = {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:7:5: note: cannot use initializer list at the beginning of a macro argument
    EXPECT_NO_THROW({
    ^               ~
arr.cpp:7:5: error: use of undeclared identifier 'EXPECT_NO_THROW'
    EXPECT_NO_THROW({
    ^
2 errors generated.
Другие вопросы по тегам