Search code examples
c++inheritanceeigenfunctor

How to use a derived functor in element wise operation in Eigen


I wanted to generalised all the custom element-wise functors that I need deriving all of them from a common base class that provides the operator() method. This way I can simply declare a variable pointing to the base class and when I call the operator() method each derived class has his own implementation. Unfortunatly I tried different approaches but I keep stumbling upon some (object slicing?) errors. Here is a code example:

#include <iostream>
#include <Eigen/Dense>
#include <Eigen/Core>

class Base {
public:
    virtual float operator()(const float& x) const = 0;
};


class Derived : public Base {
public:
    float operator()(const float& x) const;
};
float Derived::operator()(const float& x) const {
    std::cout << "Derived::operator()\n"; // you may want to toggle this
    return x;
}


int main() {

    Eigen::MatrixXf m(2, 2);
    m << 0.0f, 1.0f, -2.0f, 3.0f;

    std::cout << std::endl << m.unaryExpr(Derived()) << std::endl; // this works

    // but in a real world scenario I  have different functors inheriting from Base
    // and I would like to have a generic way to use them, like the following:
    Base* B = new Derived();

    std::cout << std::endl << (*B)(1.0f) << std::endl; // just a test, it correctly calls the Derived version of operator()

     // this doesn't work
     // from what I understand it's trying to create a Base object which of course results in an
     // error since is abstract, also when I tested with a non abstract version of Base it tried to
     // call the virtual Base::operator() instead of the derived
    std::cout << std::endl << m.unaryExpr(*B) << std::endl;
    return 0;
}

I tried looking at the doc https://tmp.mosra.cz/eigen-docs/classEigen_1_1DenseBase.html#af49806260cbfdd0be63e865367e48482 but I couldn't find the problem. Any help is appreciated.


Solution

  • By default unaryExpr takes its argument by value, which means that it will try to copy *B. Because of the type it will call the copy constructor of Base.

    This behavior is commonly not what people want and is described as "Object slicing" when it affects data members (though no data is lost in this case, it's just not using the vritual function overload you expected).

    An easy way to circumvent the copying of the functor is to use std::ref.

    Like m.unaryExpr(std::ref(*B)). This way only a reference wrapper will be copied and the underlying object is still the one you constructed.