I need a total order on both Eigen::Matrix
and Eigen::Array
, therefore I would like to specialize std::less
for the parent class of both: Eigen::DenseBase
. From https://eigen.tuxfamily.org/dox/TopicFunctionTakingEigenTypes.html I know that I must define std::less
for the templated type Eigen::DenseBase<Derived>
. Passing templated function into template function (Eigen Derived) contains a working example for the Eigen::Matrix
, so I adapted that for the parent class and came up with
template <typename Derived>
struct std::less<Eigen::DenseBase<Derived>> {
bool operator()(const Eigen::DenseBase<Derived>& lhs,
const Eigen::DenseBase<Derived>& rhs) const {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
rhs.end());
}
};
which does not work, because the compiler tries to use the default std::less
, which uses operator<
, which either does not exist (Eigen::Matrix
) or does not return a single bool (Eigen::Array
).
How to define less than (<) operator or std::less struct for Eigen::Vector3f? recommends not specializing std::less
(when using a std::map), but to explicityly provide a custom compare struct, which works
struct eigenless {
template <class Derived>
bool operator()(Eigen::DenseBase<Derived> const& lhs,
Eigen::DenseBase<Derived> const& rhs) const {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
rhs.end());
}
};
My question: How can I make a specialization of std::less work with Eigen::DenseBase? Here's a minimal example: https://godbolt.org/z/jMdGzf7hT
Disclaimer: this is not a complete answer but it's too long for mere comments.
First issue is that the specialization of less
is not in a std
namespace or a namespace embedded into std
so the specialization cannot be considered:
from cppreferences
This declaration must be in the same namespace or, for member templates, class scope as the primary template definition which it specializes.
Second issue is that Eigen::Array3d
inherits from Eigen::DenseBase<Derived>
but is not Eigen::DenseBase<Derived>
. Adding a specialization for Eigen::Array3d
make it works as below, but its not what you want to achieve.
#include <Eigen/Core>
#include <map>
namespace std {
template <class Derived>
struct less<Eigen::DenseBase<Derived>> {
bool operator()(const Eigen::DenseBase<Derived>& lhs,
const Eigen::DenseBase<Derived>& rhs) const {
return std::lexicographical_compare(lhs.cbegin(), lhs.cend(),
rhs.cbegin(), rhs.cend());
}
};
template <>
struct less<Eigen::Array3d> {
bool operator()(const Eigen::Array3d& lhs,
const Eigen::Array3d& rhs) const {
return std::lexicographical_compare(lhs.cbegin(), lhs.cend(),
rhs.cbegin(), rhs.cend());
}
};
} // namespace std
// not specializing std::less, but using custom Compare, see
// https://stackoverflow.com/questions/68321552
struct eigenless {
template <class Derived>
bool operator()(Eigen::DenseBase<Derived> const& lhs,
Eigen::DenseBase<Derived> const& rhs) const {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
rhs.end());
}
};
int main() {
Eigen::Array3d a;
Eigen::Array3d b;
auto map1 = std::map<decltype(a), int>();
map1[a] = 1;
map1[b] = 2;
auto map2 = std::map<decltype(a), int, eigenless>();
map2[a] = 1;
map2[b] = 2;
}
In your case, extending std with partial specialization seems to be allowed:
cppreference:
Adding template specializations
Class templates
It is allowed to add template specializations for any standard library class template to the namespace std only if the declaration depends on at least one program-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited.
By the way, specializing in an unrelated namespace won't work.
Regarding the inheritance issue, i'm beginning to think that it's not possible, at least without modifying the primary template parameters, which is not possible in this case.
In your case, you can yet specialize for Eigen::Array
(and Eigen::Matrix
):
#include <Eigen/Core>
#include <map>
namespace std {
template <typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>
struct less<Eigen::Array<Scalar, RowsAtCompileTime, ColsAtCompileTime>> {
bool operator()(
const Eigen::Array<Scalar, RowsAtCompileTime, ColsAtCompileTime>& lhs,
const Eigen::Array<Scalar, RowsAtCompileTime, ColsAtCompileTime>& rhs)
const {
return std::lexicographical_compare(lhs.cbegin(), lhs.cend(),
rhs.cbegin(), rhs.cend());
}
};
// template <>
// struct less<Eigen::Array3d> {
// bool operator()(const Eigen::Array3d& lhs,
// const Eigen::Array3d& rhs) const {
// return std::lexicographical_compare(lhs.cbegin(), lhs.cend(),
// rhs.cbegin(), rhs.cend());
// }
// };
} // namespace std
// not specializing std::less, but using custom Compare, see
// https://stackoverflow.com/questions/68321552
struct eigenless {
template <class Derived>
bool operator()(Eigen::DenseBase<Derived> const& lhs,
Eigen::DenseBase<Derived> const& rhs) const {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
rhs.end());
}
};
int main() {
Eigen::Array3d a;
Eigen::Array3d b;
auto map1 = std::map<decltype(a), int>();
map1[a] = 1;
map1[b] = 2;
auto map2 = std::map<decltype(a), int, eigenless>();
map2[a] = 1;
map2[b] = 2;
}
[EDIT] Eventually, a workaround is possible for the inheritance management, without modifying the primary template (which is not possible in this case). I don't find it satisfying but I give it anyway, for the record:
#include <Eigen/Core>
#include <map>
#include <type_traits>
template <typename T>
struct EigenWrapper {
static_assert(std::is_base_of<Eigen::DenseBase<T>, T>::value, "");
using type = T;
T const& val;
EigenWrapper(T const& v) : val(v){};
};
namespace std {
/// works only for 1D data, that have iterators
template <typename T>
struct less<EigenWrapper<T>> {
bool operator()(EigenWrapper<T> const& lhs,
EigenWrapper<T> const& rhs) const {
return std::lexicographical_compare(lhs.val.cbegin(), lhs.val.cend(),
rhs.val.cbegin(), rhs.val.cend());
}
};
} // namespace std
int main() {
Eigen::Array3d a;
Eigen::Array3d b;
auto map1 = std::map<decltype(EigenWrapper(a)), int>();
map1[EigenWrapper(a)] = 1;
map1[EigenWrapper(b)] = 2;
}
Live
In this snippet, I create a kind of proxy class for all classes derived from Eigen::DenseBase
and I partially specialize std::less
for these proxy. As you can see, it is not sufficient to manage both arrays and 1D data because only 1D data have an iterator interface in Eigen
it seems. But you can keep the same technic to specialize even more (std::less
specialization for EigenWrapper
can now delegate its implementation to a user-defined functor that can be specialized according to the nature of the wrapped object).
Yet in this case, its at least as much work as having a specialization for Eigen::Array
and Eigen::Matrix
. Besides client code is
less readable as it must work with EigenWrapper
objects instead of plain ones. With C++ lesser than 17, situation would be even worse as the EigenWrapper
template argument could not be deduced from constructor. You would have to explicit it or use a creator function to have argument deduction.
Nevertheless I found the technic worth mentioning because it can be applied to another class hierarchy.
Hope it will help.