Proper template initialization in Eigen

Viewed 910

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?

2 Answers

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

This seems to be a very poor design, and I would strongly recommend to revisit it, perhaps using setters with adequate names. For instance, what if you pass a MatrixXd(4,1) ??

Anyway, to answer the question, you can use sfinae to detect wether the input is a vector or a matrix at compile time:

template<typename Derived>
Container(const MatrixBase<Derived> &a, typename std::enable_if<Derived::IsVectorAtCompileTime,void*>::type* = 0): a(a), b(MatrixXd::Zero(3,3)) {
    cout << "Initializing with vector" << endl;
}

template<typename Derived>
Container(const MatrixBase<Derived> &b, typename std::enable_if<!Derived::IsVectorAtCompileTime,void*>::type* =0): a(VectorXd::Zero(3)), b(b) {
    cout << "Initializing with matrix" << endl;
}
Related