问题
Introduction
I am writing tests on Eigen matrices using Google's testing framework Google-Mock, as already discussed in another question.
With the following code I was able to add a custom Matcher
to match Eigen matrices to a given precision.
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);
}
What this does is to compare two Eigen matrices by their isApprox method, and if they don't match Google-Mock will print a corresponding error message, which will contain the expected, and the actual values of the matrices. Or, it should at least...
The Problem
Take the following simple test case:
TEST(EigenPrint, Simple) {
Eigen::Matrix2d A, B;
A << 0., 1., 2., 3.;
B << 0., 2., 1., 3.;
EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}
This test will fail because A
, and B
are not equal. Unfortunately, the corresponding error message looks like this:
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>
As you can see, Google-Test prints a hex-dump of the matrices, instead of a nicer representation of their values. The Google-documentation says the following about printing values of custom types:
This printer knows how to print built-in C++ types, native arrays, STL containers, and any type that supports the << operator. For other types, it prints the raw bytes in the value and hopes that you the user can figure it out.
The Eigen matrix comes with an operator<<. However, Google-Test, or the C++ compiler, rather, ignores it. To my understanding, for the following reason: The signature of this operator reads (IO.h (line 240))
template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);
I.e. it takes a const DenseBase<Derived>&
. The Google-test hex-dump default printer on the other hand is the default implementation of a template function. You can find the implementation here. (Follow the call-tree starting from PrintTo to see that this is the case, or prove me wrong. ;))
So, the Google-Test default printer is a better match because it takes a const Derived &
, and not only its base class const DenseBase<Derived> &
.
My Question
My question is the following. How can I tell the compiler to prefer the Eigen specific operator <<
over the Google-test hex-dump? Under the assumption that I cannot modify the class definition of the Eigen matrix.
My Attempts
So far, I've tried the following things.
Defining a function
template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);
won't work for the same reason for which operator<<
doesn't work.
The only thing which I found that worked is to use Eigen's plugin mechanism.
With a file eigen_matrix_addons.hpp
:
friend void PrintTo(const Derived &m, ::std::ostream *o) {
*o << "\n" << m;
}
and the following include directive
#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>
the test will produce the following output:
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
What's wrong with that?
For Eigen matrices this is probably an acceptable solution. However, I know that I will have to apply the same thing to other template classes, very soon, which unfortunately, do not offer a plugin mechanism like Eigen's, and whose definitions I don't have direct access to.
Hence, my question is: Is there a way to point the compiler to the right operator<<
, or PrintTo
function, without modifying the class' definition itself?
The Full 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: Further Attempts
I made some progress with an SFINAE approach.
First, I defined a trait for Eigen types. With it we can use std::enable_if
to provide template functions only for types that fulfill this trait.
#include <type_traits>
#include <Eigen/Dense>
template <class Derived>
struct is_eigen : public std::is_base_of<Eigen::DenseBase<Derived>, Derived> {
};
My first thought was to provide such a version of PrintTo
. Unfortunately, the compiler complains about an ambiguity between this function, and the Google-Test internal default. Is there a way to disambiguate and point the compiler to my function?
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;
}
}
Another approach is to overload the operator<<
for the Eigen type. It does actually work. However, the downside is that it is a global overload of the ostream operator. So, it is impossible to define any test-specific formatting (e.g. the additional new-line) without this change also affecting non-testing code. Hence, I would prefer a specialized PrintTo
like the one above.
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: Following @Alex's Answer
In the following code I implement the solution by @Alex and implement a small function that converts references of Eigen matrices to the printable type.
#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)));
}
回答1:
The problems you encounter are overload resolution problems.
google test implements a template function
namespace testing { namespace internal {
template <typename T>
void PrintTo(const T& value, std::ostream *o) { /* do smth */ }
} }
The Eigen library defines a printer function that is based on derivation. Hence
struct EigenBase { };
std::ostream& operator<< (std::ostream& stream, const EigenBase& m) { /* do smth */ }
struct Eigen : public EigenBase { };
void f1() {
Eigen e;
std::cout << e; // works
}
void f2() {
Eigen e;
print_to(eigen, &std::cout); // works
}
Both do have questionable design.
Google Test should not provide an implementation of PrintTo
but should instead check at compile time whether the user provides a PrintTo
and otherwise call a different default printing function PrintToDefault
. The PrintTo
provides is a better match than the one you provided (according to overload resolution).
on the other hand Eigen's operator<<
is based on derivation and a template function will also be preferred by overload resolution.
Eigen could provide a CRTP base class that inherits the operator<<
which a better matching type.
What you can do is inherit from eigen and provide a CRTP overload to your inherited class avoiding the issue.
#include <gtest/gtest.h>
#include <iostream>
class EigenBase {
};
std::ostream &operator<<(std::ostream &o, const EigenBase &r) {
o << "operator<< EigenBase called";
return o;
}
template <typename T>
void print_to(const T &t, std::ostream *o) {
*o << "Google Print To Called";
}
class EigenSub : public EigenBase {};
template <typename T>
struct StreamBase {
typedef T value_type;
// friend function is inline and static
friend std::ostream &operator<<(std::ostream &o, const value_type &r) {
o << "operator<< from CRTP called";
return o;
}
friend void print_to(const value_type &t, std::ostream *o) {
*o << "print_to from CRTP called";
}
};
// this is were the magic appears, because the oeprators are actually
// defined with signatures matching the MyEigenSub class.
class MyEigenSub : public EigenSub, public StreamBase<MyEigenSub> {
};
TEST(EigenBasePrint, t1) {
EigenBase e;
std::cout << e << std::endl; // works
}
TEST(EigenBasePrint, t2) {
EigenBase e;
print_to(e, &std::cout); // works
}
TEST(EigenSubPrint, t3) {
EigenSub e;
std::cout << e << std::endl; // works
}
TEST(EigenCRTPPrint, t4) {
MyEigenSub e;
std::cout << e << std::endl; // operator<< from CRTP called
}
TEST(EigenCRTPPrint, t5) {
MyEigenSub e;
print_to(e, &std::cout); // prints print_to from CRTP called
}
回答2:
Considering the OPs answer I want to do some clarifications. Unlike the derived solution from the OP I actually wanted to decorate the class instead of using a function wrapper inside the assertion.
For the sake of simplicity instead of using a google test match predicate I overloaded operator==
.
The Idea
Instead of using the Eigen class itself we use a wrapper that is a complete replacement of Eigen. So whenever we would create an instance of Eigen
we create an instance of WrapEigen
instead.
Because we do not intent to alter the implementation of Eigen
derivation is fine.
Furthermore we want to add functions to the wrapper. I do this here with multiple inheritance of functor like classes named StreamBase
and EqualBase
. We use CRTP in these functors to get the signatures right.
In order to save potential typing I used a variadic template constructor in Wrapper
. It calls the corresponding base constructor if one exists.
Working Example
#include <gtest/gtest.h>
#include <iostream>
#include <utility>
using namespace testing::internal;
struct EigenBase {
explicit EigenBase(int i) : priv_(i) {}
friend std::ostream &operator<<(std::ostream &o, const EigenBase &r) {
o << r.priv_;
return o;
}
friend bool operator==(const EigenBase& a, const EigenBase& b) {
return a.priv_ == b.priv_;
}
int priv_;
};
struct Eigen : public EigenBase {
explicit Eigen(int i) : EigenBase(i) {}
};
template <typename T, typename U>
struct StreamBase {
typedef T value_type;
typedef const value_type &const_reference;
friend void PrintTo(const value_type &t, std::ostream *o) {
*o << static_cast<const U&>(t);
}
};
template <typename T, typename U>
struct EqualBase {
typedef T value_type;
typedef const T &const_reference;
friend bool operator==(const_reference a, const_reference b) {
return static_cast<const U&>(a)
== static_cast<const U&>(b);
}
};
template <typename T, typename U>
struct Wrapper
: public T,
public StreamBase<Wrapper<T,U>, U>,
public EqualBase<Wrapper<T,U>, U> {
template <typename... Args>
Wrapper(Args&&... args) : T(std::forward<Args>(args)...) { }
};
TEST(EigenPrint, t1) {
Eigen e(10);
Eigen f(11);
ASSERT_EQ(e,f); // calls gtest::PrintTo
}
TEST(WrapEigenPrint, t1) {
typedef Wrapper<Eigen, EigenBase> WrapEigen;
WrapEigen e(10);
WrapEigen f(11);
ASSERT_EQ(e,f); // calls our own.
}
回答3:
I feel compelled to provide a new answer that I believe is simpler and better than the others, although it is so simple that I may have missed something. It's very similar to solutions that you have already tried but it's not quite the same.
Essentially, you don't have to jump through the plugin hoops of modifying the class. The caveat is that, yes, you have to define a PrintTo
function for each type (Matrix2d
, Matrix3d
, etc); a function template won't work. But since this is a unit test, I assume that you know what all your types are and so that's not an issue.
So essentially take your code from the plugin and just put it in the unit test like you were trying to do with the templated SFINAE-enabled one:
namespace Eigen
{
void PrintTo(const Matrix2d &m, std::ostream *os)
{
*os << std::endl << m << std::endl;
}
}
Nothing fancy. This works for me and should do what you want according to your test case and question.
来源:https://stackoverflow.com/questions/25146997/teach-google-test-how-to-print-eigen-matrix