Научите Google-Test печатать Eigen Matrix

Вступление

Я пишу тесты на матрицах Eigen с использованием среды тестирования 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 методи, если они не совпадают, 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 является реализацией функции шаблона. Вы можете найти реализациюВот, (Следуйте по дереву вызовов, начиная сPrintTo чтобы увидеть, что это так, или доказать, что я не прав. ;))

Таким образом, принтер Google-Test по умолчанию лучше подходит, потому что он требуетconst Derived &и не только его базовый классconst DenseBase<Derived> &.

Мой вопрос

У меня вопрос следующий. Как я могу сказать компилятору, чтобы он предпочел собственныйoperator << над гекс-дампом гугл-теста? В предположении, что я не могу изменить определение класса собственной матрицы.

Мои попытки

До сих пор я пробовал следующие вещи.

Определение функции

template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);

не будет работать по той же причине, по которойoperator<< не работает

Единственное, что я нашел, это сработало, чтобы использовать Eigen'sплагин механизм.

С файлом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.

Сначала я определил черту для собственных типов. С этим мы можем использовать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<< для собственного типа. Это действительно работает. Однако недостатком является то, что это глобальная перегрузка оператора 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 и реализую небольшую функцию, которая преобразует ссылки на собственные матрицы в печатный тип.

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

Ответы на вопрос(3)

Ваш ответ на вопрос