专门用于 Eigen::D enseBase 的 std::less

Specializing std::less for Eigen::DenseBase

提问人:j-hap 提问时间:9/27/2023 更新时间:9/28/2023 访问量:97

问:

我需要对两者的总顺序,因此我想专门研究两者的父类:。从 https://eigen.tuxfamily.org/dox/TopicFunctionTakingEigenTypes.html 我知道我必须为模板化类型定义。将模板化函数传递到模板函数(特征派生)中包含一个工作示例,因此我将其改编为父类并提出了Eigen::MatrixEigen::Arraystd::lessEigen::DenseBasestd::lessEigen::DenseBase<Derived>Eigen::Matrix

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());
    }
};

这不起作用,因为编译器尝试使用默认值 ,它使用 ,它要么不存在 () 要么不返回单个布尔值 ()。std::lessoperator<Eigen::MatrixEigen::Array

如何为 Eigen::Vector3f 定义小于 (<) 运算符或 std::less 结构?建议不要专门化(使用 std::map 时),而是显式地提供自定义比较结构,这有效std::less

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());
    }
};

我的问题是:如何使用 Eigen::D enseBase 进行 std::less 的专业化?下面是一个最小的示例:https://godbolt.org/z/jMdGzf7hT

C++ 模板 Eigen Template-specialization

评论

0赞 Some programmer dude 9/27/2023
为什么要专业化而不是超载(或使用推荐的结构)?专业化应该解决哪些其他方式无法解决的具体问题?
0赞 Oersted 9/27/2023
第一个问题,为什么需要专攻 std::less?通常,您可以制作具有相同签名的自定义比较器,并将其作为参数提供给需要此类运算符的 stl 算法。否则,名称解析可能存在问题。了解如何专门化交换操作。我记得这是众所周知的,并且可能相似
0赞 j-hap 9/27/2023
我并不严格需要 std::less 专业化,并且明确的 Compare 效果很好。我想了解,为什么专业化不起作用。
1赞 Oersted 9/27/2023
哼哼:我之前评论中描述的技术似乎不起作用。首先,less 是函子而不是函数,因此似乎不能以相同的方式触发 ADL。此外,我不确定在处理地图的命名空间中注入是否会影响“地图”。对不起,错误的轨道。我希望有人能提供更准确的信息,说明什么有效,什么无效(以及为什么)。lessoperator[]
1赞 Jarod42 9/28/2023
你不应该专注于你不拥有的东西(至少部分)。当库更新时,您有违反 ODR (NDR) 的风险(如果他们自己添加了专业化)。自定义比较结构是要走的路。std

答:

2赞 Oersted 9/27/2023 #1

免责声明:这不是一个完整的答案,但对于单纯的评论来说太长了。

第一个问题是 的专用化不在命名空间或嵌入的命名空间中,因此不能考虑专用化:
来自 cppreferences
lessstdstd

此声明必须位于同一命名空间中,或者对于成员模板,该声明必须与它专用的主模板定义位于类范围中。

第二个问题是继承自 但不是 .添加一个专业化使其工作如下,但这不是您想要实现的。Eigen::Array3dEigen::DenseBase<Derived>Eigen::DenseBase<Derived>Eigen::Array3d

#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;
}

在您的情况下,似乎允许使用部分专业化扩展 std:cppreference

添加模板专用化 类模板 仅当声明依赖于至少一个程序定义的类型并且专用化满足原始模板的所有要求时,才允许将任何标准库类模板的模板
专用化添加到命名空间 std,除非禁止此类专用化

顺便说一句,专门研究不相关的命名空间是行不通的。

关于继承问题,我开始认为这是不可能的,至少在不修改主要模板参数的情况下,在这种情况下这是不可能的。

就您而言,您仍然可以专攻(和):Eigen::ArrayEigen::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;
}

[编辑]最终,继承管理可以采用解决方法,而无需修改主模板(在这种情况下是不可能的)。我不觉得它令人满意,但我还是给它,作为记录:

#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
在这个代码片段中,我为派生自的所有类创建了一种代理类,并且我部分专用于这些代理。正如你所看到的,仅仅管理数组和一维数据是不够的,因为似乎只有一维数据有一个迭代器接口。但是,您可以保留相同的技术来进一步专业化(专业化现在可以将其实现委托给用户定义的函子,该函子可以根据包装对象的性质进行专业化)。
然而,在这种情况下,它至少与对 和 的专业化一样多。除了客户端代码是 可读性较差,因为它必须与对象而不是普通对象一起使用。如果 C++ 小于 17,情况会更糟,因为无法从构造函数中推导出模板参数。您必须显式它或使用 creator 函数来进行参数推导。
尽管如此,我发现该技术值得一提,因为它可以应用于另一个类层次结构。
Eigen::DenseBasestd::lessEigenstd::lessEigenWrapperEigen::ArrayEigen::MatrixEigenWrapperEigenWrapper

希望它会有所帮助。

评论

0赞 j-hap 9/27/2023
非常感谢您的回答!关于第一个问题的问题:我看不出专业化与编译器行为之间的区别。Matrix 和 Array 的专用化在不打开命名空间的情况下工作,而是使用限定的 id。我错过了什么/误解了什么?struct std::lessnamespace std { struct less ...
0赞 Oersted 9/27/2023
删除失败,在我这边按预期:godbolt.org/z/KWxTrcKoe。你的编译器是什么?您的编译选项?namespace std
0赞 j-hap 9/27/2023
当然,如果两者都不存在,它就会失败,但我想知道为什么在您的示例中您明确打开了 而不是像我的最小示例中那样仅使用限定的结构名称。我理解你的第一句话,好像有必要打开命名空间,合格是不够的std::namespace std {namespace stdstd::lessstd::less
1赞 Oersted 9/28/2023
对不起,我没有找到一个参考资料,说明使用范围解析名称声明新的专用化与在命名空间中声明它相同。那里需要一名语言律师。
0赞 Misha T 9/27/2023 #2

编译器不明白这是尝试编译代码的时候。当你使用它时,它没有意义,因为可以转换为.如果你想使用 spezialized ,你可以使用这样的东西Eigen::Array3dEigen::DenseBase<Derived>struct eigenlessEigen::Array3dEigen::DenseBase<Derived>std::less

template<>
template<typename _Scalar, int d>
struct std::less<Eigen::Array<_Scalar, d, 1>> {
    bool operator()(const Eigen::Array<_Scalar, d, 1>& lhs,
                    const Eigen::Array<_Scalar, d, 1>& rhs) const {
        return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
                                           rhs.end());
    }
};