P
I had some more thoughts on this yesterday evening and I am still convinced that its is best to invalidate the si<0,0,0> case and let each operation return a double, that would give a scalar instead. I forgot to mention that the compiler prefers non-template methods before he considers instantiating a template, so in the example
si<1,0,0> fifteenmeters(15);
si<1,-1,0> fivemeterspersecond(5);
si<0,1,0> twoseconds(2);
double x = fifteenmeters / (fivemeterspersecond * twoseconds);
we get the following:
- fifteenmeterspersecond*twoseconds is done via the template operator* and gives an si<1,0,0>
- the division of si<1,0,0>/si<1,0,0> matches the nontemplate operator/ that gives a double, so the compiler would not even try to instantiate the template operator that gives si<0,0,0> but just does the conversion for you by applying just the right operator.
One next step I thought of: In my example, an output operator is missing. How would you output something like si<1,0,0> (0.66e-11)? It depends, if you prefer meters or nanometers (thats 660 nanometers, if you have an application that deals with optics this would be the natural unit). And even defining a variable that holds this value doesn't look very nice and readable in your code. But consider the following:
//quantities
typedef si<1,0,0> Length;
typedef si<2,0,0> Area;
typedef si<3,0,0> Volume;
typedef si<1,-1,0> Velocity;
typedef si<1,-2,0> Acceleration;
typedef si<2,-2,1> Energy;
/* ... */
//units
const Length meter = Length(1);
const Length centimeter = Length(0.01);
const Length nanometer = Length(1e-9);
const Length inch = Length(0.0254);
const Energy Joule = Energy(1);
const Volume litre = Volume(1e-3);
const Area hektar = Area(1e4);
const Energy GeV = Energy(1.602176462e-13);
/* ... */
//constants
const Acceleration ggrav = Acceleration(9.81); //Earth surface acceleration
const si<3,-2,-1> GNewton = si<3,-2,-1>(6.67428e-11); //Newtons gravitational constant
/* ... */
Looks like a lot of work, and it is. But calculations look like this:
//calculate the volume of a pyramid
int main()
{
Area base = 10 * centimeter * centimeter; // 10 cm²
Length heigth = 15 * inch; //15 inch
Volume vPyramid = base * height / 3;
cout << "The Volume is " << vPyramid / litre << " Litres\n"
<< "That is " << vPyramid / (meter*meter*meter) << " cubic meters\n"
<< "or " << vPyramid / (inch*inch*inch) << " cubic inches" << endl;
}
As you see, the operator<< does not need to be overloaded, its just doubles!
Looks much better than
//calculate the volume of a pyramid
int main()
{
si<2,0,0> base = si<2,0,0>(0.001);
si<1,0,0> heigth = si<1,0,0>(15*0.0254); //15 inch
si<3,0,0> vPyramid = base * height / 3;
cout << "The Volume is " << vPyramid << " in whatever Unit operator<< makes the output";
}
So, alltogether, you don't need error-prone implicit conversion operators to double if you convert automatically to doubles when its right, and you don't need to overload operator<< for the si<>'s if you provide the units and constants and even the code looks nice and readable.