I have implemented my own SI Unit class. When using arithmetic operations the resulting SI Unit can change. E.g: ( meter / second ) / meter = 1 / second.
Ok now I have also created a simple 3D Vector class. This vector shall be generic and also usable with my SI Unit class. So I implemented a simple division operator. See the following code:
// Determine result type of Lhs / Rhs:
template < class Lhs, class Rhs >
struct TV3TypeV3Div { typedef BOOST_TYPEOF( Lhs( ) / Rhs( ) ) type; };
// Vector / Vector:
template < class Lhs, class Rhs >
RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type > operator/( const RobotTools::DataTypes::TV3Type< Lhs >& lhs,
const RobotTools::DataTypes::TV3Type< Rhs >& rhs )
{
// do something useful
return RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type >( 0, 0, 0 );
}
// Vector / Vector
RobotTools::DataTypes::TV3Type< Tools::DataTypes::Length > vl;
vl / vl; // Ok this works
During compile time the right return type will be determined by using the TV3TypeV3Div struct. This works.
Now I want to extend the operators. I want also calculate vectors with scalar types. So I wrote this operator:
// Vector / Scalar
template < class Lhs, class Rhs >
RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Rhs >::type > operator/( const RobotTools::DataTypes::TV3Type< Lhs >& lhs,
const Rhs& rhs )
{
// do something useful
return RobotTools::DataTypes::TV3Type< typename TV3TypeV3Div< Lhs, Tools::DataTypes::Length >::type >( 0, 0, 0 );
}
// Vector / Scalar
RobotTools::DataTypes::TV3Type< Tools::DataTypes::Length > vl;
Tools::DataTypes::Length sl;
vl / sl; // Ok nice it works too
So far so good. The problem is when I define the second operator (Vector/Scalar) this operator is so generic that the compiler wants to use it also for Vector/Vector division. But it fails because Lhs( ) / Rhs( ) with:
Lhs=Tools::DataTypes::Length and Rhs=RobotTools::DataTypes::TV3Type
is not defined. This is correct and I understand the given error. What I do not understand is that the compiler does NOT use the Vector/Vector operator.
The compiler does not want to use the Vector/Scalar operator for Vector/Vector division. It just wants to make check if there's a match.
If you declare (but not define) a completely generic division operator
template <typename Lhs, typename Rhs>
struct InvalidDivision {};
template <typename Lhs, typename Rhs>
InvalidDivision<Lhs, Rhs>
operator/(const Lhs& lhs, const Rhs& rhs); // do not define
then your code should compile and link. The Vector/Scalar overload will be considered and rejected because Vector/Vector is a better match.
The downside of it is if you do divide things that do not have division defined for them, the compiler will complain about InvalidDivision<something,other>
instead of giving its usual error.
Perhaps this could be improved by using SFINAE or some other advanced magic.
Update: a more detailed explanation
What is going on in the original version of the code?
We are trying to invoke a division operator for a user-defined type. There are two functions named operator/
and the compiler considers both and tries to find out which one is a better match.
First consider the Vector/Vector operator/
. The compiler deduces that both Lhs
and Rhs
template parameters are Length
(I omit namespaces for brevity). It then substitutes them into parameters of TV3TypeV3Div
, computes the insides of TV3TypeV3Div
, determines that TV3TypeV3Div<Lhs,Rhs>::type
is also Length
, and finally computes the return type of operator/
which is TV3Type<Length>
.
Now consider the Vector/Scalar operator/
. The compiler deduces that Lhs
is Length
, but Rhs
is TV3Type<Length>
. It then substitutes these types into parameters of TV3TypeV3Div
, and tries to compute the insides of TV3TypeV3Div
. Here's where things break up: there/s no operator/
that would accept Length
and TV3Type<Length>
. So it is impossible to compute TV3TypeV3Div<Lhs,Rhs>::type
. The compiler outputs an error.
Now consider what happens when the generic operator/
is declared. Now there is an operator/
that would accept Length
and TV3Type<Length>
! (Or any other pair of arguments, for that matter). So the compiler happily computes TV3TypeV3Div<Lhs,Rhs>::type
and then the return type of Vector/Scalar operator/
. So now it is possible to consider both Vector/Vector and Vector/Scalar operator/
overloads.
The compiler now also sees and considers the generic operator/
for the overload resolution. All three overloads produce a match. But the Vector/Vector overload wins, because it is more specialized then either Vector/Scalar or generic operator/
, and is therefore a better match.
Update 2
It should be possible to do this:
namespace Detail
{
template <typename Lhs, typename Rhs>
struct DoNotUse {};
template <typename Lhs, typename Rhs>
DoNotUse<Lhs, Rhs> operator/(const Lhs& lhs, const Rhs& rhs);
template < class Lhs, class Rhs >
struct TV3TypeV3Div { typedef BOOST_TYPEOF( Lhs( ) / Rhs( ) ) type; };
}
using Detail::TV3TypeV3Div;
This should nicely hide the generic operator/ from everyone but TV3TypeV3Div
.