Search code examples
c++templatesdesign-patternspolymorphismeigen

Covariant return type on Eigen Matrix for base class method


Suppose that I have two different solvers that both will be called at run time.

  • I want to call solvers' api and get resulted Eigen matrix through the base class pointer.
  • The solved matrix size are selected from a few known values depending on some runtime variable. I need to use compiled time fixed size matrix in this case.

The solver derived class definitions are as follow.

template <int VarSize>
class SolverA : Solver {
 public:
  SolverA() {}

  bool Solve() override {
    // SolverA specific implementations.
  }
  const Eigen::Matrix<double, VarSize, 1>& solution() override {
    return solution_;
  }
 private:
  Eigen::Matrix<double, VarSize, 1> solution_;
}


template <int VarSize>
class SolverB : Solver {
 public:
  SolverB() {}

  bool Solve() override {
    // SolverB specific implementations.
  }
  const Eigen::Matrix<double, VarSize, 1>& solution() override {
    return solution_;
  }
 private:
  Eigen::Matrix<double, VarSize, 1> solution_;
}

Now the problem is how I can write the base class solution() method to get the resulted Eigen matrix, since the template parameter VarSize are not known in the base declaration.

class Solver {
 public:
  virtual bool Solve() = 0;
  // not covariant type, won't compile.
  // virtual const Eigen::VectorXd& solution() = 0;
}

constexpr int kEasyProbSize = 5;
constexpr int kHardProbSize = 20;

int main() {
  Solver* solver;

  if (GetComplexity() > 30) {
    solver = &SolverB<kHardProbSize>();
  } else {
    solver = &SolverA<kEasyProbSize>();
  }
  solver->Solve();
  std::cout << solver->solution() << std::endl;
}

Some thoughts:

  • Eigen::Matrix<double, VarSize, 1> does not derive from Eigen::VectorXd so it cannot override.
  • I also cannot use const Eigen::MatrixBase<Derived>& since there is no that derived information and virtual method won't allow template.
  • I need to call from base class pointer so I cannot make the base class a template class.
  • The solved solution_ is already allocated and doesn't make sense to return a copy or converted to a dynamic size matrix.

Is this fixed size matrix getting from base class pointer even possible?


Solution

  • The easiest solution would be to just store a VectorXd solution_; inside Solver itself. But if you insist on storing the actual solution vector only in the derived classes, you can have solution() return an Eigen::Ref<const Eigen::VectorXd> which can be created with just moving a pointer and a few integers:

    class Solver {
     public:
      virtual bool Solve() = 0;
      // not covariant type, won't compile.
      using VecRef = Eigen::Ref<const Eigen::VectorXd> const;
      virtual VecRef solution() = 0;
    };
    
    
    template <int VarSize>
    class SolverA : public Solver {
     public:
      SolverA() {}
    
      bool Solve() override {
        // SolverA specific implementations.
        return true;
      }
      Solver::VecRef solution() override {
        return solution_;
      }
     private:
      Eigen::Matrix<double, VarSize, 1> solution_;
    };
    
    
    template <int VarSize>
    class SolverB : public Solver {
     public:
      SolverB() {}
    
      bool Solve() override {
        // SolverB specific implementations.
        return true;
      }
       VecRef solution() override {
        return solution_;
      }
     private:
      Eigen::Matrix<double, VarSize, 1> solution_;
    };
    

    Godbolt demo: https://godbolt.org/z/MqaofsoK9