Reputation: 494
I am trying to integrate boost.units
into Eigen
, following the 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).
The issues I am having is that there seems to be some ambiguity regarding the multiplication operator when multiplying two matrices with custom scalar types based on boost::units
.
This behaviour occurs with clang
10.0.0.3 and Apple clang
11.0.3.
The code that triggers the error (use of overloaded operator '*' is ambiguous
) is the following:
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;
For the integration I created files containing NumTraits
for boost::units::quantity<T>
as follows:
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
And for the multiplication support I have the following struct
definitions:
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
The full error output:
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 ¶meters;
^
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.
Upvotes: 2
Views: 879
Reputation: 392929
Ignoring the conjugate product traits, I can see that the third specialization seems off:
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;
};
The line:
scalarA, units::quantity<scalarB>,
Ought to read:
units::quantity<scalarA>, units::quantity<scalarB>,
That third specialization we just fixed should obviously be the one matching our case. However, the other specializations do not exclude the case where both scalars are a quantity.
Commenting out the irrelevant two indeed makes things pass:
#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";
}
Prints
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
I would try to combine all three in a single specializatio. However ScalarBinaryOpTraits
is not SFINAE-friendly. I tried to get something going nonetheless:
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
Which does work like earlier: Live On Compiler Explorer
However, if we change the &&
into ||
(or !=
for XOR logic) we get ambiguity again. Not sure how to proceed, but maybe it gives you ideas?
Upvotes: 2