Search code examples
c++templateseigen

Proper template initialization in Eigen


Concerning proper parameter type deduction in Eigen.

I want to create a class that can either be initialized with a Matrix(MatrixXd) or Vector(VectorXd). The issue is that MatrixXd and VectorXd are both typedefs in Eigen:

// roughly
typedef Matrix<double, Dynamic, 1> VectorXd;
typedef Matrix<double, Dynamic, Dynamic> MatrixXd;

If I try to have two constructors overloaded with VectorXd and MatrixXd, I get Ambiguous Constructor call error.

Consider the following example:

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

using namespace std;
using namespace Eigen;

class Container {
public:
    VectorXd a;
    MatrixXd b;

    Container(VectorXd a): a(a), b(MatrixXd::Zero(3,3)) {
        cout << "Initializing with vector" << endl;
    }

    Container(MatrixXd b): a(VectorXd::Zero(3)), b(b) {
        cout << "Initializing with matrix" << endl;
    }
};

int main() {
    Container x(VectorXd::Ones(4));
    cout << x.a << endl;
    cout << x.b << endl;

    Container y(MatrixXd::Ones(4, 4));
    cout << y.a << endl;
    cout << y.b << endl;

    return 0;
}

The exact error I get:

main.cpp:23:15: error: call to constructor of 'Container' is ambiguous
Container x(Matrix<double, Dynamic, 1>::Ones(4));
          ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:13:5: note: candidate constructor
Container(Matrix<double, Dynamic, 1> a): a(a), b(MatrixXd::Zero(3,3)) {
^
main.cpp:17:5: note: candidate constructor
Container(Matrix<double, Dynamic, Dynamic> b): a(VectorXd::Zero(3)), b(b) {

So the question is: How could I resolve which constructor I want to call?


Solution

  • perfect forwarding, sfinae constrained constructors do work for your specific use case:

    template<class T>
    Container(T&& a,decltype(VectorXd{std::forward<T>(a)})* = 0): a(std::forward<T>(a)), b(MatrixXd::Zero(3,3)) {}
    
    template<class T>
    Container(T&& b,decltype(MatrixXd{std::forward<T>(a)})* = 0): a(VectorXd::Zero(3)), b(std::forward<T>(b)){}
    

    but I'd suggest against using it (or anything equivalent), because there's no guarantee this will work for any matrix expression or for these expressions in future Eigen versions, because these conversions are not guaranteed to be sfinae friendly as far as I know.

    I think it's better to provide more sensible overloads (depending on your actual use case), maybe something like

    class Container {
    public:
        VectorXd a;
        MatrixXd b;
    
        struct DefaultedT{};
        static constexpr DefaultedT Defaulted{};
    
        Container(VectorXd a,MatrixXd b): a(std::move(a)), b(std::move(b)) {}
        Container(VectorXd a,DefaultedT): a(std::move(a)), b(MatrixXd::Zero(3,3)) {}
        Container(DefaultedT,MatrixXd b): a(VectorXd::Zero(3)), b(std::move(b)) {}
    };
    
    int main() {
        Container x(VectorXd::Ones(4),Container::Defaulted);
        Container y(Container::Defaulted,MatrixXd::Ones(4, 4));
        ...
    

    or just write a single constructor taking a matrix expression and perform the initialization logic accordingly ...