1

I am trying to write some functions with template specialization for Eigen types, following this question: Eigen: type deduction in template specialization of base-class

I wrote this:

#include <type_traits>
#include <Eigen/Core>

namespace isEigenPlainObjectBaseDetail {
    template <typename T>
    std::true_type test(const Eigen::PlainObjectBase<T>);
    std::false_type test(...);
}
template <typename T>
struct isEigenPlainObjectBase :
        public decltype(isEigenPlainObjectBaseDetail::test(std::declval<T>())) {};

template <typename T, typename Enable = void>
void foo(T& obj) {
    std::cout << "Generic Called!\n";
}

template <typename T, typename std::enable_if<isEigenPlainObjectBase<T>::value>::type>
void foo(T& obj) {
    std::cout << "Eigen Specialization Called!";
}

int main() {
    Eigen::MatrixXd m;
    Eigen::VectorXd v;
    int i = 0;
    foo(i);
    foo(m);
    foo(v);
    return 0;
}

but this calls everytime the generic function:

Generic Called!
Generic Called!
Generic Called!

What am I doing wrong? How can I write template specializations for functions with Eigen matrices and vectors?

max66
  • 65,235
  • 10
  • 71
  • 111

2 Answers2

0

Not sure this is the only problem (no Eigen installed, so I can't check; sorry) but...

You can partial specialize structs and classes, not functions.

So this code

template <typename T, typename Enable = void>
void foo(T& obj) {
    std::cout << "Generic Called!\n";
}

template <typename T, typename std::enable_if<isEigenPlainObjectBase<T>::value>::type>
void foo(T& obj) {
    std::cout << "Eigen Specialization Called!";
}

doesn't work and I'm surprised that compile.

EDIT: as pointed by Quentin (thanks!) the second template paramenter of the second one is a non-type one; so there isn't collision between the two foo() but the second one is ever excluded because the non-type template parameter is of type void. And this is not allowed.

To use SFINAE, you should pass through a class or a struct; something like

template <typename T, typename = void>
struct foo
 {
   static void func (T & obj)
    { std::cout << "Generic Called!\n"; }
 };

template <typename T
struct foo<T, typename std::enable_if<isEigenPlainObjectBase<T>::value>::type>
 {
   static void func (T & obj)
    { std::cout << "Eigen Specialization Called!"; } 
 };

and can be used as

foo<decltype(i)>::func(i);
foo<decltype(m)>::func(m);
foo<decltype(v)>::func(v);

Maybe you can define a bar() template helper function

template <typename T>
void bar (T & t)
 { foo<T>::func(t); }

and simply call

bar(i);
bar(m);
bar(v);

Or maybe it's simpler use tag dispatching.

If you define a couple of foo() receiving a different additional parameter

template <typename T>
void foo (T &, std::false_type const &)
 { std::cout << "Generic Called!\n" }

template <typename T>
void foo (T &, std::true_type const &)
 { std::cout << "Eigen Specialization Called!"; }

you should be able to select the correct foo() with a bar() helper function like this

template <typename T>
void bar (T & t)
 { foo(t, isEigenPlainObjectBase<T>{}); }

and, as before, simply calling

bar(i);
bar(m);
bar(v);

[caution: not tested]

max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    I reckon that the second function is an overload, which is valid because the second template parameter is a non-type one. However, it is always SFINAE'd out because `std::enable_if<>::type` is `void`, which is not allowed. – Quentin Dec 24 '17 at 18:36
  • @Quentin - I didn't noticed wasn't `typename = typename std::enable_if` but only `typename std::enable_if`; answer modified to explicit this; thanks! – max66 Dec 24 '17 at 18:43
  • ok, good to know! :D I compiled it with gcc, didn't tried it with msvc. but of course it wasn't working! I was hoping in something designed in the Eigen library which allows to discriminate all the eigen plain object base using templates! – Alessandro Muntoni Dec 25 '17 at 10:31
  • @AlessandroMuntoni - so, if I understand correctly, you have a problem with `isEigenPlainObjectBase`; sorry but I don't now Eigen enough. At worst, you can implement a type traits that identify the desired types one by one. – max66 Dec 25 '17 at 18:46
  • @max66 yes, right now I have a specialization for all the Eigen Matrix types I use, and every specialization is calling a function with a different name.. I was hoping there was a better and more elegant way :( – Alessandro Muntoni Dec 26 '17 at 22:36
0

If you simply want to specialize a function for the two types that you are passing in it is likely much simpler than you are thinking.

Current code:

template <typename T, typename Enable = void>
void foo(T& obj) {
    std::cout << "Generic Called!\n";
}

template <typename T, typename std::enable_if<isEigenPlainObjectBase<T>::value>::type>
void foo(T& obj) {
    std::cout << "Eigen Specialization Called!";
}

New Code:

template <typename T>
void foo(T& obj);

template <>
void foo(Eigen::MatrixXd& obj) {
    std::cout << "Eigen Specialization Called!" << std::endl;
}

template <>
void foo(Eigen::VectorXd& obj) {
    std::cout << "Eigen Specialization Called!" << std::endl;
}

template <typename T>
void foo(T& obj) {
    std::cout << "Generic Called!" << std::endl;
}

Note, not compiled to test.

Another way to do this is to add a trait to your interested types, how might you do that?

The way that I would recommend is the following. (Note not compiled/tested)

template<typename T>
struct isEigenTrait;

//specializations for all of the eigen types you are interested in
template<>
struct isEigenTrait<Eigen::MatrixXd> {
   typedef std::true_type value;
};

//...

template<typename T>
struct isEigenTrait {
   typedef std::false_type value;
};

template<class T, std::enable_if_t<isEigenTrait<T>::value, true_type> = 0>
void foo(T& obj) {
    std::cout << "Eigen Specialization Called!" << std::endl;
}

template<class T, std::enable_if_t<isEigenTrait<T>::value, false_type> = 0>
void foo(T& obj) {
    std::cout << "Generic Called!" << std::endl;
}

Code modified from c++ reference so should work.

To get all of the Eigen Matrices in a single trait I believe the following should work.

//specializations for all of the eigen types you are interested in
template<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>
struct isEigenTrait< Eigen::Matrix<Scalar, RowsAtCompileTime, ColsAtCompileTime> > {
   typedef std::true_type value;
};

You will likely need to do the same for each of the template types you want the isEigenTrait adding to.

ceorron
  • 1,230
  • 1
  • 17
  • 28
  • 1
    I was already using this solution, but it was leading to a lot of duplication of code. I use a lot of different Eigen matrices (Row major or Column major, static or dynamic), and for this case I was hoping to a single specialization to "catch" all these matrices! – Alessandro Muntoni Dec 25 '17 at 10:23
  • If you really want to do that check out the documentation on enable_if http://en.cppreference.com/w/cpp/types/enable_if – ceorron Dec 25 '17 at 23:26
  • See modified answer above. – ceorron Dec 26 '17 at 17:48
  • Thank you! However, also in this way I should write a specialization for all templated Eigen Matrix types.. I'm trying to understand if there is a way to catch all the Eigen Matrix types in a single specialization (meybe using Eigen::PlainObjectBase), but discriminating from a generic template type T... – Alessandro Muntoni Dec 26 '17 at 22:32
  • See the modified answer for a way to "catch" all of the Eigen Matrices with a single trait. I don't believe using a base class works in this case. This is because templates don't decay using the object hierarchy like normal c++ code does and so that approach doesn't work. Please mark this answer as correct if it leads to the correct answer. – ceorron Dec 27 '17 at 12:59