Введение
Я пишу тесты на собственных матрицах, используя платформу Google для тестирования Google-Mock, как уже обсуждалось в другом вопросе.
С помощью следующего кода мне удалось добавить пользовательский Matcher
для соответствия матриц Eigen с заданной точностью.
MATCHER_P2(EigenApproxEqual, expect, prec,
std::string(negation ? "isn't" : "is") + " approx equal to" +
::testing::PrintToString(expect) + "\nwith precision " +
::testing::PrintToString(prec)) {
return arg.isApprox(expect, prec);
}
Это делается для сравнения двух собственных матриц по методу isApprox
, и если они не совпадают с Google-Mock, напечатать соответствующее сообщение об ошибке, которое будет содержать ожидаемые и фактические значения матриц. Или, должно быть, по крайней мере...
Проблема
Возьмем следующий простой тестовый пример:
TEST(EigenPrint, Simple) {
Eigen::Matrix2d A, B;
A << 0., 1., 2., 3.;
B << 0., 2., 1., 3.;
EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}
Этот тест завершится неудачно, потому что A
и B
не равны. К сожалению, соответствующее сообщение об ошибке выглядит следующим образом:
gtest_eigen_print.cpp:31: Failure
Value of: A
Expected: is approx equal to32-byte object <00-00 00-00 00-00 00-00 00-00 00-00 00-00 F0-3F 00-00 00-00 00-00 00-40 00-00 00-00 00-00 08-40>
with precision 1e-07
Actual: 32-byte object <00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-40 00-00 00-00 00-00 F0-3F 00-00 00-00 00-00 08-40>
Как вы можете видеть, Google-Test печатает шестнадцатеричный дамп матриц вместо лучшего представления их значений. Документация Google сообщает следующее о печати значений пользовательских типов:
Этот принтер знает, как печатать встроенные типы С++, собственные массивы, STL контейнеры и любой тип, который поддерживает < оператор. Для других типы, он печатает необработанные байты в ценности и надеется, что вы пользователь может понять это.
Собственная матрица поставляется с operator<<
. Однако Google-Test или компилятор С++ игнорируют его. По моему мнению, по следующей причине: подпись этого оператора гласит (IO.h(строка 240))
template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);
т.е. он принимает значение const DenseBase<Derived>&
. С другой стороны, стандартным принтером Google-test с шестнадцатеричным дампом является реализация функции шаблона по умолчанию. Здесь вы можете найти реализацию здесь. (Следуйте за деревом вызова, начинающимся с PrintTo, чтобы убедиться, что это так, или доказать, что я ошибаюсь.;))
Итак, принтер Google-Test по умолчанию лучше, потому что он принимает const Derived &
, а не только его базовый класс const DenseBase<Derived> &
.
Мой вопрос
Мой вопрос следующий. Как я могу сказать компилятору, чтобы он предпочел Eigen-специфический operator <<
по сравнению с тестовым шестнадцатеричным дампом Google? В предположении, что я не могу изменить определение класса собственной матрицы.
Мои попытки
До сих пор я пробовал следующие вещи.
Определение функции
template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);
не будет работать по той же причине, для которой operator<<
не работает.
Единственное, что я нашел, что работал, это использовать механизм .
С файлом eigen_matrix_addons.hpp
:
friend void PrintTo(const Derived &m, ::std::ostream *o) {
*o << "\n" << m;
}
а следующая директива
#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>
тест даст следующий результат:
gtest_eigen_print.cpp:31: Failure
Value of: A
Expected: is approx equal to
0 2
1 3
with precision 1e-07
Actual:
0 1
2 3
Что не так с этим?
Для собственных матриц это, вероятно, приемлемое решение. Тем не менее, я знаю, что мне придется применить одно и то же к другим классам шаблонов, очень скоро, которые, к сожалению, не предлагают плагин-механизм, такой как Eigen, и чьи определения у меня нет прямого доступа.
Следовательно, мой вопрос: есть ли способ указать компилятору на право operator<<
или PrintTo
функцию без изменения самого определения класса?
Полный код
#include <Eigen/Dense>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
// A GMock matcher for Eigen matrices.
MATCHER_P2(EigenApproxEqual, expect, prec,
std::string(negation ? "isn't" : "is") + " approx equal to" +
::testing::PrintToString(expect) + "\nwith precision " +
::testing::PrintToString(prec)) {
return arg.isApprox(expect, prec);
}
TEST(EigenPrint, Simple) {
Eigen::Matrix2d A, B;
A << 0., 1., 2., 3.;
B << 0., 2., 1., 3.;
EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}
Изменить: дальнейшие попытки
Я сделал некоторый прогресс с помощью подхода SFINAE.
Сначала я определил черту для типов Eigen. С его помощью мы можем использовать std::enable_if
для предоставления функций шаблона только для типов, которые соответствуют этому признаку.
#include <type_traits>
#include <Eigen/Dense>
template <class Derived>
struct is_eigen : public std::is_base_of<Eigen::DenseBase<Derived>, Derived> {
};
Моя первая мысль заключалась в предоставлении такой версии PrintTo
. К сожалению, компилятор жалуется на двусмысленность между этой функцией и внутренним дефолтом Google-Test. Есть ли способ устранить ошибку и указать компилятор на мою функцию?
namespace Eigen {
// This function will cause the following compiler error, when defined inside
// the Eigen namespace.
// gmock-1.7.0/gtest/include/gtest/gtest-printers.h:600:5: error:
// call to 'PrintTo' is ambiguous
// PrintTo(value, os);
// ^~~~~~~
//
// It will simply be ignore when defined in the global namespace.
template <class Derived,
class = typename std::enable_if<is_eigen<Derived>::value>::type>
void PrintTo(const Derived &m, ::std::ostream *o) {
*o << "\n" << m;
}
}
Другой подход - перегрузить operator<<
для типа Eigen. Это действительно работает. Однако недостатком является то, что это глобальная перегрузка оператора ostream. Таким образом, невозможно определить какое-либо тестовое форматирование (например, дополнительную новую строку) без этих изменений, также влияющих на не тестируемый код. Следовательно, я бы предпочел специализированный PrintTo
, подобный приведенному выше.
template <class Derived,
class = typename std::enable_if<is_eigen<Derived>::value>::type>
::std::ostream &operator<<(::std::ostream &o, const Derived &m) {
o << "\n" << static_cast<const Eigen::DenseBase<Derived> &>(m);
return o;
}
Изменить: после ответа @Alex
В следующем коде я реализую решение @Alex и реализую небольшую функцию, которая преобразует ссылки собственных матриц в тип печати.
#include <Eigen/Dense>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
MATCHER_P(EigenEqual, expect,
std::string(negation ? "isn't" : "is") + " equal to" +
::testing::PrintToString(expect)) {
return arg == expect;
}
template <class Base>
class EigenPrintWrap : public Base {
friend void PrintTo(const EigenPrintWrap &m, ::std::ostream *o) {
*o << "\n" << m;
}
};
template <class Base>
const EigenPrintWrap<Base> &print_wrap(const Base &base) {
return static_cast<const EigenPrintWrap<Base> &>(base);
}
TEST(Eigen, Matrix) {
Eigen::Matrix2i A, B;
A << 1, 2,
3, 4;
B = A.transpose();
EXPECT_THAT(print_wrap(A), EigenEqual(print_wrap(B)));
}