Вступление
Я пишу тесты на собственных матрицах, используя платформу тестирования Google Google-Mock, как уже обсуждалось в еще вопрос.
С помощью следующего кода я смог добавить пользовательский Matcher
для сопоставления собственных матриц с заданной точностью.
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
метода, и если они not match 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 говорится следующее о печати значений пользовательских типов:
Этот принтер умеет печатать встроенные типы C ++, собственные массивы, контейнеры STL и любой тип, поддерживающий оператор ‹*. Для других типов он печатает необработанные байты в значении и надеется, что вы, пользователь, сможете это понять.
Матрица Эйгена поставляется с operator<<
. Однако Google-Test или, скорее, компилятор C ++ игнорирует это. Насколько я понимаю, по следующей причине: подпись этого оператора гласит (IO.h (строка 240))
template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);
Т.е. требуется const DenseBase<Derived>&
. С другой стороны, принтер Google-test hex-dump по умолчанию является реализацией функции шаблона по умолчанию. Вы можете найти реализацию здесь. (Следуйте дереву вызовов, начиная с PrintTo, чтобы убедиться, что это так, или докажите, что я не прав.;))
Таким образом, принтер Google-Test по умолчанию лучше подходит, потому что он принимает const Derived &
, а не только его базовый класс const DenseBase<Derived> &
.
Мой вопрос
У меня следующий вопрос. Как я могу сказать компилятору, что предпочтение отдается специфичному для Eigen operator <<
, а не шестнадцатеричному дампу Google-test? При условии, что я не могу изменить определение класса матрицы Eigen.
Мои попытки
Пока что пробовал следующее.
Определение функции
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;
}
и следующая директива include
#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.
Во-первых, я определил черту для собственных типов. С его помощью мы можем использовать 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)));
}
enable_if
, чтобы проверить, был ли ваш класс производным от этой пустой базы, а затем предложить соответствующую перегрузку? - person Alexander Oh   schedule 07.08.2014PrintTo
так же, как и с плагином Eigen. Но мне кажется, что обертка - это крайнее средство. Поскольку обычно очень утомительно следить за тем, чтобы он правильно воспроизводил интерфейс. - person Lemming   schedule 07.08.2014#define EXPECT_EIGEN_EQ(A, B) EXPECT_THAT(print_wrap(A), EigenEqual(print_wrap(B)));
помогает - person   schedule 22.07.2016