具有自定义标量类型的特征值:具有自定义类型的矩阵乘法因“使用重载运算符‘*’不明确”而失败

Eigen with custom scalar types: Matrix multiplication with custom type fails with `use of overloaded operator '*' is ambiguous`

我正在尝试将 boost.units 集成到 Eigen 中,遵循 official documentation. I've also looked at two projects that have done the same, and I've tested the operation with their code as well, although I'm getting the same error (Project 1 Project 2).

我遇到的问题是,在将两个矩阵与基于 boost::units.

的自定义标量类型相乘时,乘法运算符似乎存在一些歧义

此行为发生在 clang 10.0.0.3 和 Apple clang 11.0.3。

触发错误(use of overloaded operator '*' is ambiguous)的代码如下:

    const Eigen::Matrix<boost::units::quantity<boost::units::si::length, double>, 2, 3> x;
    const Eigen::Matrix<boost::units::quantity<boost::units::si::dimensionless, double>, 3, 1> y;
    const Eigen::Matrix<boost::units::quantity<boost::units::si::length, double>, 2, 1> result = x * y;

为了集成,我为 boost::units::quantity<T> 创建了包含 NumTraits 的文件,如下所示:

namespace Eigen {

template<class T> struct NumTraits<boost::units::quantity<T>> : NumTraits<double> {
    typedef boost::units::quantity<T> Real;
    typedef boost::units::quantity<T> NonInteger;
    typedef boost::units::quantity<T> Nested;

    enum {
        IsComplex             = 0,
        IsInteger             = 0,
        IsSigned              = 1,
        RequireInitialization = 1,
        ReadCost              = 1,
        AddCost               = 3,
        MulCost               = 3
    };
};
} // namespace Eigen

对于乘法支持,我有以下 struct 定义:

namespace Eigen {

namespace units = boost::units;

template<typename scalarA, typename scalarB>
struct ScalarBinaryOpTraits<units::quantity<scalarA>, scalarB,
    internal::scalar_product_op<units::quantity<scalarA>, scalarB>> {
    typedef units::quantity<typename units::multiply_typeof_helper<scalarA, scalarB>::type> ReturnType;
};

template<typename scalarA, typename scalarB>
struct ScalarBinaryOpTraits<scalarA, units::quantity<scalarB>,
    internal::scalar_product_op<scalarA, units::quantity<scalarB>>> {
    typedef units::quantity<typename units::multiply_typeof_helper<scalarA, scalarB>::type> ReturnType;
};

template<typename scalarA, typename scalarB>
struct ScalarBinaryOpTraits<scalarA, units::quantity<scalarB>,
    internal::scalar_product_op<units::quantity<scalarA>, units::quantity<scalarB>>> {
    typedef units::quantity<typename units::multiply_typeof_helper<scalarA, scalarB>::type> ReturnType;
};

template<typename scalarA, typename scalarB>
struct ScalarBinaryOpTraits<units::quantity<scalarA>, scalarB,
    internal::scalar_conj_product_op<units::quantity<scalarA>, scalarB>> {
    typedef typename units::multiply_typeof_helper<scalarA, scalarB>::type ReturnType;
};

template<typename scalarA, typename scalarB>
struct ScalarBinaryOpTraits<scalarA, units::quantity<scalarB>,
    internal::scalar_conj_product_op<scalarA, units::quantity<scalarB>>> {
    typedef typename units::multiply_typeof_helper<scalarA, scalarB>::type ReturnType;
};

template<typename scalarA, typename scalarB>
struct ScalarBinaryOpTraits<units::quantity<scalarA>, units::quantity<scalarB>,
    internal::scalar_conj_product_op<units::quantity<scalarA>, units::quantity<scalarB>>> {
    typedef units::quantity<typename units::multiply_typeof_helper<scalarA, scalarB>::type> ReturnType;
};

} // namespace Eigen

完整的错误输出:

Scanning dependencies of target LaserCalibration
[  8%] Building CXX object CMakeFiles/LaserCalibration.dir/maths/Maths.cpp.o
/Users/dwright/projects/dv-laser-calibration/maths/Maths.cpp:9:97: error: use of overloaded operator '*' is ambiguous (with operand types 'const Eigen::Matrix<boost::units::quantity<boost::units::si::length, double>, 2, 3>' (aka 'const Matrix<quantity<unit<list<dim<boost::units::length_base_dimension, static_rational<1> >, boost::units::dimensionless_type>, homogeneous_system<list<boost::units::si::meter_base_unit, boost::units::list<boost::units::scaled_base_unit<boost::units::cgs::gram_base_unit, boost::units::scale<10, static_rational<3> > >, boost::units::list<boost::units::si::second_base_unit, boost::units::list<boost::units::si::ampere_base_unit, boost::units::list<boost::units::si::kelvin_base_unit, boost::units::list<boost::units::si::mole_base_unit, boost::units::list<boost::units::si::candela_base_unit, boost::units::list<boost::units::angle::radian_base_unit, boost::units::list<boost::units::angle::steradian_base_unit, boost::units::dimensionless_type> > > > > > > > > > >, double>, 2, 3>') and 'const Eigen::Matrix<boost::units::quantity<boost::units::si::dimensionless, double>, 3, 1>' (aka 'const Matrix<quantity<unit<boost::units::dimensionless_type, homogeneous_system<list<boost::units::si::meter_base_unit, boost::units::list<boost::units::scaled_base_unit<boost::units::cgs::gram_base_unit, boost::units::scale<10, static_rational<3> > >, boost::units::list<boost::units::si::second_base_unit, boost::units::list<boost::units::si::ampere_base_unit, boost::units::list<boost::units::si::kelvin_base_unit, boost::units::list<boost::units::si::mole_base_unit, boost::units::list<boost::units::si::candela_base_unit, boost::units::list<boost::units::angle::radian_base_unit, boost::units::list<boost::units::angle::steradian_base_unit, boost::units::dimensionless_type> > > > > > > > > > >, double>, 3, 1>'))
        const Eigen::Matrix<boost::units::quantity<boost::units::si::length, double>, 2, 1> result = x * y;
                                                                                                     ~ ^ ~
/usr/local/include/eigen3/Eigen/src/Core/../plugins/CommonCwiseBinaryOps.h:50:29: note: candidate function [with T = Eigen::Matrix<boost::units::quantity<boost::units::unit<boost::units::list<boost::units::dim<boost::units::length_base_dimension, boost::units::static_rational<1, 1> >, boost::units::dimensionless_type>, boost::units::homogeneous_system<boost::units::list<boost::units::si::meter_base_unit, boost::units::list<boost::units::scaled_base_unit<boost::units::cgs::gram_base_unit, boost::units::scale<10, static_rational<3> > >, boost::units::list<boost::units::si::second_base_unit, boost::units::list<boost::units::si::ampere_base_unit, boost::units::list<boost::units::si::kelvin_base_unit, boost::units::list<boost::units::si::mole_base_unit, boost::units::list<boost::units::si::candela_base_unit, boost::units::list<boost::units::angle::radian_base_unit, boost::units::list<boost::units::angle::steradian_base_unit, boost::units::dimensionless_type> > > > > > > > > >, void>, double>, 2, 3, 0, 2, 3>]
EIGEN_MAKE_SCALAR_BINARY_OP(operator*,product)
                            ^
/usr/local/include/eigen3/Eigen/src/Core/../plugins/CommonCwiseBinaryOps.h:50:29: note: candidate function [with T = Eigen::Matrix<boost::units::quantity<boost::units::unit<boost::units::dimensionless_type, boost::units::homogeneous_system<boost::units::list<boost::units::si::meter_base_unit, boost::units::list<boost::units::scaled_base_unit<boost::units::cgs::gram_base_unit, boost::units::scale<10, static_rational<3> > >, boost::units::list<boost::units::si::second_base_unit, boost::units::list<boost::units::si::ampere_base_unit, boost::units::list<boost::units::si::kelvin_base_unit, boost::units::list<boost::units::si::mole_base_unit, boost::units::list<boost::units::si::candela_base_unit, boost::units::list<boost::units::angle::radian_base_unit, boost::units::list<boost::units::angle::steradian_base_unit, boost::units::dimensionless_type> > > > > > > > > >, void>, double>, 3, 1, 0, 3, 1>]
/usr/local/include/eigen3/Eigen/src/Core/MatrixBase.h:166:5: note: candidate function [with OtherDerived = Eigen::Matrix<boost::units::quantity<boost::units::unit<boost::units::dimensionless_type, boost::units::homogeneous_system<boost::units::list<boost::units::si::meter_base_unit, boost::units::list<boost::units::scaled_base_unit<boost::units::cgs::gram_base_unit, boost::units::scale<10, static_rational<3> > >, boost::units::list<boost::units::si::second_base_unit, boost::units::list<boost::units::si::ampere_base_unit, boost::units::list<boost::units::si::kelvin_base_unit, boost::units::list<boost::units::si::mole_base_unit, boost::units::list<boost::units::si::candela_base_unit, boost::units::list<boost::units::angle::radian_base_unit, boost::units::list<boost::units::angle::steradian_base_unit, boost::units::dimensionless_type> > > > > > > > > >, void>, double>, 3, 1, 0, 3, 1>]
    operator*(const MatrixBase<OtherDerived> &other) const;
    ^
/Users/dwright/projects/dv-laser-calibration/maths/Maths.cpp:6:106: warning: unused parameter 'event' [-Wunused-parameter]
Eigen::Matrix<boost::units::quantity<double>, 2, 1> Maths::convertToPhysicalCoordinates(const dv::Event &event) const {
                                                                                                         ^
In file included from /Users/dwright/projects/dv-laser-calibration/maths/Maths.cpp:1:
/Users/dwright/projects/dv-laser-calibration/maths/Maths.hpp:16:25: warning: private field 'parameters' is not used [-Wunused-private-field]
        CalibrationParameters &parameters;
                               ^
2 warnings and 1 error generated.
make[2]: *** [CMakeFiles/LaserCalibration.dir/maths/Maths.cpp.o] Error 1
make[1]: *** [CMakeFiles/LaserCalibration.dir/all] Error 2
make: *** [all] Error 2
11:04:41: The process "/usr/local/Cellar/cmake/3.17.2/bin/cmake" exited with code 2.
Error while building/deploying project LaserCalibration (kit: Imported Kit)
When executing step "CMake Build"
11:04:41: Elapsed time: 00:02.

忽略共轭乘积特征,我可以看出第三个专业化似乎不对:

template <typename scalarA, typename scalarB>
    struct ScalarBinaryOpTraits<
    scalarA, units::quantity<scalarB>,
    internal::scalar_product_op<units::quantity<scalarA>,
    units::quantity<scalarB>>> {
        typedef units::quantity<
            typename units::multiply_typeof_helper<scalarA, scalarB>::type>
            ReturnType;
    };

行:

    scalarA, units::quantity<scalarB>,

应该阅读:

    units::quantity<scalarA>, units::quantity<scalarB>,

消除原因

我们刚刚修复的第三个专业化显然应该符合我们的情况。但是,其他特化不排除两个标量都是一个量的情况。

注释掉不相关的两个确实让事情过去了:

Live On Compiler Explorer

#include <boost/units/unit.hpp>
#include <boost/units/io.hpp>
#include <boost/units/limits.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si.hpp>
#include <boost/units/cmath.hpp>
#include <Eigen/Core>

namespace Eigen {
    namespace units = boost::units;

#if 0
    template <typename scalarA, typename scalarB>
        struct ScalarBinaryOpTraits<
        units::quantity<scalarA>, scalarB,
        internal::scalar_product_op<units::quantity<scalarA>, scalarB>> {
            typedef units::quantity<
                typename units::multiply_typeof_helper<scalarA, scalarB>::type>
                ReturnType;
        };
#endif

#if 0
    template <typename scalarA, typename scalarB>
        struct ScalarBinaryOpTraits<
        scalarA, units::quantity<scalarB>,
        internal::scalar_product_op<scalarA, units::quantity<scalarB>>> {
            typedef units::quantity<
                typename units::multiply_typeof_helper<scalarA, scalarB>::type>
                ReturnType;
        };
#endif

#if 1
    template <typename scalarA, typename scalarB>
        struct ScalarBinaryOpTraits<
        units::quantity<scalarA>, units::quantity<scalarB>,
        internal::scalar_product_op<units::quantity<scalarA>,
        units::quantity<scalarB>>> {
            typedef units::quantity<
                typename units::multiply_typeof_helper<scalarA, scalarB>::type>
                ReturnType;
        };
#endif
} // namespace Eigen

#include <iostream>
int main() {
    using V = boost::units::quantity<boost::units::si::length, double>;
    using U = boost::units::quantity<boost::units::si::dimensionless, double>;
    constexpr auto Vu = boost::units::si::meter;
    constexpr auto Uu = 1.0;

    Eigen::Matrix<V, 2, 3> x;
    Eigen::Matrix<U, 3, 1> y;

    x << 1*Vu, 2*Vu, 3*Vu, 4*Vu, 5*Vu, 6*Vu;
    y << 1*Uu, 2*Uu, 3*Uu;

    std::cout << "x:\n" << x << "\n";
    std::cout << "y:\n" << y << "\n";
    auto result = (x * y).eval();

    std::cout << "result:\n" << result << "\n";
}

版画

x:
  1 m   2 m   3 m
  4 m   5 m   6 m
y:
              1 dimensionless
              2 dimensionless
              3 dimensionless
result:
  14 m
  32 m

修复?

我会尝试将这三个专业结合起来。但是 ScalarBinaryOpTraits 对 SFINAE 不友好。尽管如此,我还是试图让事情继续下去:

namespace Eigen {
    namespace units = boost::units;

    namespace /*file-static*/ {
        template <typename A, typename B, typename R = units::multiply_typeof_helper<A, B> >
        struct QuantityProductImpl {
            using ReturnType = typename R::type;
        };
    }

    template <typename A, typename B>
        struct ScalarBinaryOpTraits<A, B,
            std::enable_if_t< units::is_quantity<A>::value && units::is_quantity<B>::value,
                internal::scalar_product_op<A, B>
            >
        > : QuantityProductImpl<A, B> { };
} // namespace Eigen

哪个像之前那样工作:Live On Compiler Explorer

但是,如果我们将 && 更改为 ||(或 XOR 逻辑的 !=),我们会再次出现歧义。不确定如何进行,但也许它会给您一些想法?