7

I am writing a generic class that utilizes Eigen data types. I already have problems assigning constructor arguments to class member variables. A simplified version of my code would be:

template <typename Derived>
class A
{
public:
  Eigen::Matrix<Derived> M; // error C2976: too few template parameters

A(const Eigen::DenseBase<Derived> & V)
{
  M = V.eval(); // I would want to snapshot the value of V.
}
};

My question is now what data type M should be? I tried a variety of options, such as:

Eigen::internal::plain_matrix_type_column_major<Derived> M;
Eigen::DenseBase<Derived> M;

but they just generate different errors. Note that I use C++17 and expect the class template parameter to be inferred from the constructor.

chtz
  • 17,329
  • 4
  • 26
  • 56
  • *'too few template parameters'* – if `Eigen::Matrix` appears to be correct type for you, then stay with, but find out what the [other parameters](https://eigen.tuxfamily.org/dox/classEigen_1_1Matrix.html) are used for and apply them appropriately (hint: you might need further template parameters for your own class, too). – Aconcagua Apr 01 '19 at 13:06
  • How do you intend to use `A`? What types can `Derived` have? You could try [`Derived::PlainObject`](http://eigen.tuxfamily.org/dox/classEigen_1_1DenseBase.html#aae45af9b5aca5a9caae98fd201f47cc4), if you want to store any `Derived` type in the corresponding plain type (this automatically derives the scalar type, dimensions, etc). – chtz Apr 01 '19 at 14:19
  • I expect Derived to by a dense Matrix/Array. `V` can have fixed, dynamic, or mixed sizes. `M` is intended to just store the values of `V`, which is the reason for the `.eval()`. In general, `A` will compute new values based on `V` but I need to keep the input and other intermediate results for later use. – Thomas Auzinger Apr 01 '19 at 14:23
  • Can you post a [mcve] of how you intend to use `A`? I guess you essentially want to write `A a{some_expression};`, right? – chtz Apr 01 '19 at 14:58

3 Answers3

5

The declaration of the Eigen::Matrix variable M should be something like:

Eigen::Matrix<typename Derived::Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> M; 

Then your code will compile. See Demo.

The detailed description of each of the template parameters can be found at this link.

P.W
  • 26,289
  • 6
  • 39
  • 76
  • This is certainly possible but what would happen if the input matrix is fixed size? I assume that this information would get lost when making everything dynamic. – Thomas Auzinger Apr 01 '19 at 13:16
  • You can also specify integer values instead of `Eigen::Dynamic`. See: https://godbolt.org/z/JztB4N – P.W Apr 01 '19 at 13:34
  • This would be the clearest way to do it if the Matrix size is known when the class is constructed. In my case, I intend to write a class that can take both fixed size and dynamic arguments. I cannot enforce certain matrix sizes when writing the class. – Thomas Auzinger Apr 01 '19 at 13:42
  • You can use `Derived::RowsAtCompileTime`, etc. But then you could also simply use `typename Derived::PlainObject` or `typename Derived::PlainMatrix`. – chtz Apr 02 '19 at 11:23
3

A generic way of declaring your type would be to use the declaration generating it as a source, this way you don't have to look at specific ways to declare complicated template types, this is an example based on your code:

decltype(static_cast<Eigen::DenseBase<Derived> *>(nullptr)->eval()) M;

don't worry there is no nullptr dereference here because the code inside decltype is not executed.

As pointed in the comments there's a cleaner way of writing this:

decltype(declval<Eigen::DenseBase<Derived>>().eval()) M;

and if you're worried that the type might be a reference and don't want that:

remove_reference_t<decltype(declval<Eigen::DenseBase<Derived>>().eval())> M;

also don't forget to #include <type_traits> and either prefix everything with std:: or add using namespace std; to your code.

To make the syntax simpler for future use add this to the beginning of your code:

template<typename T, typename T::f>
using member_function_return_t = remove_reference_t<decltype(declval<T>().f())>;

and than declare the variable as:

member_function_return_t<Eigen::DenseBase<Derived>, Eigen::DenseBase<Derived>::eval> M;
xception
  • 4,241
  • 1
  • 17
  • 27
  • I tested this and it seems to work. I was not aware of this methodology. I was hoping that Eigen provides something more straightforward to obtain the type of a matrix or an expression. – Thomas Auzinger Apr 01 '19 at 13:44
  • 1
    If `Derived` is already of `Matrix` type, this will result in a const-reference to the type (which may or may not what the OP wants). – chtz Apr 01 '19 at 14:20
  • 1
    I believe `std::declval` was introduced to avoid such constructs ;) – R2RT Apr 01 '19 at 14:33
  • included both your observations in the answer – xception Apr 02 '19 at 08:26
  • Honestly, I don't see any advantage vs just writing `typename Derived::PlainObject M`. – chtz Apr 02 '19 at 11:24
  • @chtz I was just trying to offer a solution that would work in any other case with any other class without requiring knowing the internals of how something works - a generic solution to declaring variables to store data returned from a templated class member function call. The "advantage" the way I use this is that my knowledge of internals of a library I use can be minuscule, thus significantly reducing my research time into variable declarations. P.S. I never even used eigen. – xception Apr 03 '19 at 09:16
2

Your container needs the actual "plain type" as template argument:

template <typename PlainType>
class A
{
    PlainType M; 
public:
    template<class Derived>
    A(const Eigen::MatrixBase<Derived> & V) : M(V) {}
};

And you need an additional template deduction rule:

template<class Derived>
A(const Eigen::MatrixBase<Derived> & V) -> A<typename Derived::PlainObject>;

Usage example (on godbolt):

template<class X>
void bar(X&); // just to read full type of A

void foo(Eigen::Matrix2d const& M)
{
    A a = M*M;
    bar(a);  // calls bar<A<Matrix2d>>();
}
chtz
  • 17,329
  • 4
  • 26
  • 56