21

I am currently designing an API where I want that the user to be able to write code like this:

PowerMeter.forceVoltage(1 mV);
PowerMeter.settlingTime(1 ms);

Currently we do this using defines like:

#define mV *1.0e-03

This makes it very convenient for the user to write their code and it is also very readable, but of course has also drawbacks:

int ms;

Will throw some compiler errors which are hard to understand. So I am looking for a better solution.

I tried the new C++11 literals, but with this all I could achieve is:

long double operator "" _mV(long double value) {
  return value * 1e-3;
}
PowerMeter.forceVoltage(1_mV);

In the end the API does not care about the unit like Volt or second but only takes the number, so I don't want to do any checking if you really input Volts in forceVoltage or not. So this should also be possible:

PowerMeter.forceVoltage(2 ms);

Any idea besides staying with the defines?

phw
  • 213
  • 1
  • 12
schnaufi
  • 213
  • 2
  • 5
  • 2
    Can you pass your units as a separate variable? `PowerMeter.forceVoltage(2, "ms");` Or maybe the entire expression as a string? – Blender Jun 14 '12 at 06:13
  • I could do this, but it is not the natural way the user would like to program. – schnaufi Jun 14 '12 at 06:15
  • 9
    Why... So you want to specify measure units but don't want to check they're correct? It doesn't make sense. Furthermore, your 'user' knows C++ but cares about 'natural way'?.. Almost unthinkable. Why not just include it to function name? – keltar Jun 14 '12 at 06:29
  • No, actually most of the users are not really c++ programmers. Thus we want to keep the API simple. Including in the function name is dificult, because than we would have forceVolt() forceMiliVolt() ... – schnaufi Jun 14 '12 at 10:55
  • Note that all you are doing is attaching SI prefixes, not units (milli := 1e-03 is _not_ a unit, it's a value/prefix). – Frank Jun 14 '12 at 14:45
  • I really think You should check mr Stroustrup lecture: http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style (check at 18m45s and further) – baderman Jul 04 '12 at 11:09

9 Answers9

18

how about instead turning it around a bit by creating classes (ms,mV) for the different currents

e.g.

PowerMeter.forceVoltage( mV(1) );  
PowerMeter.settlingTime( ms(1) )

It is pretty clear to the user and arguably not hard to read plus you would get type checking for free. having a common base class for the different units would make it easier to implement.

AndersK
  • 35,813
  • 6
  • 60
  • 86
  • 3
    Boost.Date_Time uses this approach for its `time_duration` types (http://www.boost.org/doc/libs/1_49_0/doc/html/date_time/posix_time.html#date_time.posix_time.time_duration). – Emile Cormier Jun 14 '12 at 06:41
9

You can see the library "C++ Units" from Calum Grant as a good example of how to implement this. The library is a bit outdated, but still worth to see or may be to use.

Also, i think it might be interesting to read: "Applied Template Metaprogramming in SI UNITS: the Library of Unit-Based Computation"

There is one more good library: UDUNITS-2 which:

contains a C library for units of physical quantities and a unit-definition and value-conversion utility.

Sergei Danielian
  • 4,938
  • 4
  • 36
  • 58
  • 2
    +1 Beyond ratios, the **dimensions** are the most important parts. With the dimensions settled, the ratios (almost) come for free. – Matthieu M. Jun 14 '12 at 06:40
8

You could use C++11's compile-time rational arithmetic support for the units, instead of defining literals or macros for the units.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
6

Take a look at Boost.Units. Here's some example code:

quantity<energy>
work(const quantity<force>& F, const quantity<length>& dx)
{
    return F * dx; // Defines the relation: work = force * distance.
}

...

/// Test calculation of work.
quantity<force>     F(2.0 * newton); // Define a quantity of force.
quantity<length>    dx(2.0 * meter); // and a distance,
quantity<energy>    E(work(F,dx));  // and calculate the work done.
Danko Durbić
  • 7,077
  • 5
  • 34
  • 39
  • There's also [PhysUnits-CT-Cpp11](https://github.com/martinmoene/PhysUnits-CT-Cpp11), a small C++11, C++14 header-only library for compile-time dimensional analysis and unit/quantity manipulation and conversion. Simpler than Boost.Units, only depends on standard C++ library, SI-only, integral powers of dimensions. – Martin Moene Jul 02 '16 at 16:24
2

Here's what I came up with... pretty much the same idea as Anders K, but since I wrote the code, I'll post it:

#include <iostream>

using namespace std;

class MilliVoltsValue;
class VoltsValue;

class VoltsValue
{
public:
   explicit VoltsValue(float v = 0.0f) : _volts(v) {/* empty */}
   VoltsValue(const MilliVoltsValue & mV);

   operator float() const {return _volts;}

private:
   float _volts;
};

class MilliVoltsValue
{
public:
   explicit MilliVoltsValue(float mV = 0.0f) : _milliVolts(mV) {/* empty */}
   MilliVoltsValue(const VoltsValue & v) : _milliVolts(v*1000.0f) {/* empty */}

   operator float() const {return _milliVolts;}

private:
   float _milliVolts;
};

VoltsValue :: VoltsValue(const MilliVoltsValue & mV) : _volts(mV/1000.0f) {/* empty */}

class PowerMeter
{
public:
   PowerMeter() {/* empty */}

   void forceVoltage(const VoltsValue & v) {_voltsValue = v;}
   VoltsValue getVoltage() const {return _voltsValue;}

private:
   VoltsValue _voltsValue;
};

int main(int argc, char ** argv)
{
   PowerMeter meter;

   meter.forceVoltage(VoltsValue(5.0f));
   cout << "Current PowerMeter voltage is " << meter.getVoltage() << " volts!" << endl;

   meter.forceVoltage(MilliVoltsValue(2500.0f));
   cout << "Now PowerMeter voltage is " << meter.getVoltage() << " volts!" << endl;

   // The line below will give a compile error, because units aren't specified
   meter.forceVoltage(3.0f);   // error!

   return 0;
}
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
2

I prefer avoiding macros where ever I can, and this is an example where it should be possible. One lightweight solution that gives you correct dimensions would be:

static double m = 1;
static double cm = 0.1;
static double mV = 0.001;

double distance = 10*m + 10*cm;

This also reflects the physical concept that units are something that's multiplied with the value.

Thomas M.
  • 1,496
  • 1
  • 10
  • 21
  • 2
    Additional comment: You might want to use a namespace for this, and the user of your API can then decide if he prefers cluttering his namespace or doing a setVoltage(10 * CoolUnits::m). But still better than defining stuff. – Thomas M. Jun 14 '12 at 12:21
  • 1
    Cute with little overhead, but its not enough. The problem with this is that $1m \neq 1$, but to $m$. That is, $1m = 100 cm$ but the unit themselves are also a term. This is an issue when a person tries to add a temperature to a distance. Your solution would allow it. –  Sep 22 '17 at 02:06
1

Consider using an enum for your units and pass it as a second parameter:

namespace Units
{
    enum Voltage
    {
        millivolts = -3,
        volts = 0,
        kilovolts = 3
    };

    enum Time
    {
        microseconds = -6,
        milliseconds = -3,
        seconds = 0
    };
}

class PowerMeter
{
public:
    void forceVoltage(float baseValue, Units::Voltage unit)
    {
         float value = baseValue * std::pow(10, unit);
         std::cout << "Voltage forced to " << value << " Volts\n";
    }

    void settlingTime(float baseValue, Units::Time unit)
    {
         float value = baseValue * std::pow(10, unit);
         std::cout << "Settling time set to " << value << " seconds\n";
    }
}

int main()
{
    using namespace Units;
    PowerMeter meter;
    meter.settlingTime(1.2, seconds);
    meter.forceVoltage(666, kilovolts);
    meter.forceVoltage(3.4, milliseconds); // Compiler Error
}

Wrapping the Units namespace around the enums avoids polluting the global namespace with the unit names. Using enums in this way also enforces at compile time that the proper physical unit is passed to the member functions.

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
  • 2
    Seeing as how the OP tried C++11 literals, `enum class` would be more appropriate. – chris Jun 14 '12 at 06:35
  • @chris: With `enum class`, there's no way to pull in all the unit names in the current scope with a `using` directive. So you'd have to prefix every unit (e.g. `Voltage::volt`). – Emile Cormier Jun 14 '12 at 06:46
  • That's true. I guess it just depends on what kind of code the user is looking to write. – chris Jun 14 '12 at 16:17
1

I prefer the solution from Anders K, however you may use a template to save some time implementing all units as a separte class which can be timeconsuming and prone to errors as you may need to write a lot of code by hand:

enum Unit {
    MILI_VOLT = -3,
    VOLT = 0,
    KILO_VOLT = 3
};

class PowerMeter
{
public:

    template<int N>
    void ForceVoltage(double val)
    {
        std::cout << val * pow(10.0, N) << endl;
    };
};

Use like this:

        PowerMeter pm;
        pm.ForceVoltage<MILI_VOLT>(1);
        pm.ForceVoltage<VOLT>(1);
        pm.ForceVoltage<KILO_VOLT>(1);
Alex
  • 5,240
  • 1
  • 31
  • 38
1

Before you go crazy with anything more complicated, whenever you write new code that takes a quantity as an argument you should name your methods like this so that it's 100% clear:

PowerMeter.forceInMilliVolts( ... )
PowerMeter.settlingTimeInSeconds( ... )

And similarly use variables with the right names e.g.:

int seconds(10);
int milliVolts(100);

This way it does not matter if you have to convert, it is still clear what you are doing e.g.

PowerMeter.settlingTimeInSeconds( minutes*60 );

When you are ready with something more powerful move to that, if you really need to, but make sure you do not lose the clarity of which unit is being used.

Benedict
  • 2,771
  • 20
  • 21