Bringen Sie Google-Test bei, wie die Eigenmatrix gedruckt wird

Einführung

Ich schreibe Tests für Eigenmatrizen mit Googles Test-Framework Google-Mock, wie bereits in besprocheneine andere Frage.

Mit dem folgenden Code konnte ich eine benutzerdefinierte hinzufügenMatcher Eigenmatrizen auf eine bestimmte Genauigkeit abzustimmen.

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

Das bedeutet, dass zwei Eigenmatrizen nach ihren Werten verglichen werdenisApprox Methode, und wenn sie nicht mit Google-Mock übereinstimmen, wird eine entsprechende Fehlermeldung ausgegeben, die den erwarteten und den tatsächlichen Wert der Matrizen enthält. Oder sollte es zumindest ...

Das Problem

Nehmen Sie den folgenden einfachen Testfall:

TEST(EigenPrint, Simple) {
    Eigen::Matrix2d A, B;
    A << 0., 1., 2., 3.;
    B << 0., 2., 1., 3.;

    EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}

Dieser Test schlägt fehl, weilA, undB&nbsp;sind nicht gleich. Die entsprechende Fehlermeldung sieht leider so aus:

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>

Wie Sie sehen, druckt Google-Test ein Hex-Dump der Matrizen anstelle einer schöneren Darstellung ihrer Werte. DasGoogle-Dokumentation&nbsp;sagt Folgendes über das Drucken von Werten benutzerdefinierter Typen:

Dieser Drucker kann integrierte C ++ - Typen, systemeigene Arrays, STL-Container usw. druckenJeder Typ, der den Operator << unterstützt. Bei anderen Typen werden die unformatierten Bytes im Wert gedruckt, und der Benutzer hofft, dass Sie es herausfinden können.

Die Eigenmatrix enthält eineoperator<<. Google-Test oder der C ++ - Compiler ignorieren es jedoch eher. Meines Erachtens aus folgendem Grund: Die Unterschrift dieses Betreibers lautet (IO.h (Zeile 240))

template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);

Das heißt es braucht eineconst DenseBase<Derived>&. Der Google-Test-Hex-Dump-Standarddrucker ist dagegen die Standardimplementierung einer Vorlagenfunktion. Sie finden die ImplementierungHier. (Folgen Sie dem Anrufbaum abPrintTo&nbsp;um zu sehen, dass dies der Fall ist, oder um mir das Gegenteil zu beweisen. ;))

Daher ist der Standarddrucker von Google-Test besser geeignet, da er eineconst Derived &und nicht nur seine Basisklasseconst DenseBase<Derived> &.

Meine Frage

Meine Frage ist folgende. Wie kann ich dem Compiler mitteilen, dass er das Eigen-Spezifische bevorzugen soll?operator <<&nbsp;über den Google-Test Hex-Dump? Unter der Annahme, dass ich die Klassendefinition der Eigenmatrix nicht ändern kann.

Meine Versuche

Bisher habe ich folgende Dinge ausprobiert.

Eine Funktion definieren

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

wird nicht aus dem gleichen Grund arbeiten, für dieoperator<<&nbsp;funktioniert nicht

Das Einzige, was ich als funktionierend empfand, war die Verwendung von EigensPlugin-Mechanismus.

Mit einer Akteeigen_matrix_addons.hpp:

friend void PrintTo(const Derived &m, ::std::ostream *o) {
    *o << "\n" << m;
}

und die folgende Include-Direktive

#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>

Der Test erzeugt die folgende Ausgabe:

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
Was stimmt damit nicht?

Für Eigenmatrizen ist dies wahrscheinlich eine akzeptable Lösung. Ich weiß jedoch, dass ich das Gleiche sehr bald auf andere Template-Klassen anwenden muss, die leider keinen Plugin-Mechanismus wie den von Eigen bieten und auf deren Definitionen ich keinen direkten Zugriff habe.

Daher lautet meine Frage: Gibt es eine Möglichkeit, den Compiler nach rechts zu richtenoperator<<, oderPrintTo&nbsp;Funktion, ohne die Definition der Klasse selbst zu ändern?

Der vollständige Code
#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));
}
Edit: Weitere Versuche

Ich habe einige Fortschritte mit einem SFINAE-Ansatz erzielt.

Zuerst habe ich ein Merkmal für Eigenarten definiert. Damit können wir verwendenstd::enable_if&nbsp;Vorlagenfunktionen nur für Typen bereitzustellen, die diese Eigenschaft erfüllen.

#include <type_traits>
#include <Eigen/Dense>

template <class Derived>
struct is_eigen : public std::is_base_of<Eigen::DenseBase<Derived>, Derived> {
};

Mein erster Gedanke war, eine solche Version vonPrintTo. Leider beschwert sich der Compiler über eine Mehrdeutigkeit zwischen dieser Funktion und dem internen Standard von Google-Test.Gibt es eine Möglichkeit, den Compiler zu disambiguieren und auf meine Funktion zu verweisen?

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

Ein weiterer Ansatz ist die Überlastung deroperator<<&nbsp;für den Eigen-Typ. Es funktioniert tatsächlich. Der Nachteil ist jedoch, dass es sich um eine globale Überlastung des ostream-Betreibers handelt. Daher ist es unmöglich, eine testspezifische Formatierung (z. B. die zusätzliche neue Zeile) zu definieren, ohne dass sich diese Änderung auch auf nicht testenden Code auswirkt. Daher würde ich ein spezialisiertes bevorzugenPrintTo&nbsp;wie oben.

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;
}
Edit: Nach @ Alex Antwort

Im folgenden Code implementiere ich die Lösung von @Alex und implementiere eine kleine Funktion, die Referenzen von Eigenmatrizen in den druckbaren Typ konvertiert.

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