Teach Google-Test how to print Eigen Matrix

孤街浪徒 提交于 2019-11-30 03:53:26

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
}

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!