Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Units for types in C++

Tags:

In the C++ Core Guidlines P.1 change_speed example, it shows a Speed type that is used as shown below:

change_speed(Speed s); // better: the meaning of s is specified
// ...
change_speed(2.3); // error: no unit
change_speed(23m / 10s); // meters per second

I am particularly interested in the last two lines of this example. The first seems to suggest that if you provide no units with the argument to change_speed it will throw an error. The last line shows the units defined using some the m and s literals. Are both of these new features in modern versions of C++? If so, how would something like this be implemented, and what version of C++ is required?

like image 410
rozzy Avatar asked Aug 21 '18 07:08

rozzy


People also ask

What is Unit type C++?

The unit type resembles the void type in languages such as C# and C++. The unit type has a single value, and that value is indicated by the token () . The value of the unit type is often used in F# programming to hold the place where a value is required by the language syntax, but when no value is needed or desired.

What is data types in C?

Types of Data Types in CFloating-point, integer, double, character. Derived Data Type. Union, structure, array, etc. Enumerated Data Type. Enums.

What is size of char in C?

Char Size. The size of both unsigned and signed char is 1 byte always, irrespective of what compiler we use.

What is the size of int data type in C?

The size of int is usually 4 bytes (32 bits). And, it can take 232 distinct states from -2147483648 to 2147483647 .


2 Answers

As mentioned in the comments, the example from the core guidelines uses user-defined literals to construct application-specific types that intuitively represent physical quantities. To illustrate them for the specific example, consider these types:

/* "Strong" speed type, unit is always [m/s]. */
struct Speed {
   long double value;
};

/* "Strong" length type, parameterized by a unit as multiples of [m]. */    
template <class Period = std::ratio<1>> struct Length {
   unsigned long long value;
};

It probably doesn't make much sense to track the unit of Length objects, but not for Speed instances, but let's consider the simplest possible example here. Now, let's look at two user-defined literals:

#include <ratio>

auto operator ""_m(unsigned long long n)
{
   return Length<>{n};
}

auto operator ""_km(unsigned long long n)
{
   return Length<std::kilo>{n};
}

They let you instantiate Length objects like this:

/* We use auto here, because the suffix is so crystal clear: */
const auto lengthInMeter = 23_m;
const auto lengthInKilometer = 23_km;

In order to cosntruct a Speed instance, let's define an appropriate operator for dividing a Length by a duration:

#include <chrono>

template <class LengthRatio, class Rep, class DurationRatio>
auto operator / (const Length<LengthRatio>& lhs,
      const std::chrono::duration<Rep, DurationRatio>& rhs)
{
   const auto lengthFactor = static_cast<double>(LengthRatio::num)/LengthRatio::den;
   const auto rhsInSeconds = std::chrono::duration_cast<std::chrono::seconds>(rhs);

   return Speed{lengthFactor*lhs.value/rhsInSeconds.count()};
}

Now, let's look at the example from the core guidelines again,

void change_speed(const Speed& s)
{
    /* Complicated stuff... */
}

but most importantly, how you can call such a function:

using namespace std::chrono_literals;

int main(int, char **)
{
   change_speed(23_m/1s);
   change_speed(42_km/3600s);
   change_speed(42_km/1h);

   return 0;
}

As @KillzoneKid mentioned in the comments, C++11 is required for this to work.

like image 127
lubgr Avatar answered Sep 21 '22 14:09

lubgr


There are two different things involved in your code:

  1. The use of strong/unit types to make your code more robust, i.e., you differentiate two integer types. This is built-in in some languages (e.g., Ada), but not in C++, but you can create classes that wraps integer types to mimic such behavior (see below).

  2. The use of operator literals to create instances of such classes in user-friendly way, i.e., you write 1s instead of seconds{1}. This is simply a convenience feature, which can be useful in some places.

Using strong integer types is very useful because it makes your code much less error prone*:

  • You cannot convert between types representing durations and lengths, as in real life.
  • Within types representing the same kind of things (e.g., seconds and hours), there are not implicit conversions if you lose precision, e.g., you cannot convert seconds to hours, unless you are representing our with floating point type (float / double).
  • The (implicit and non-implicit) conversions takes care of the scaling for you: you can convert hours to seconds without having to manually multiply by 3600.
  • You can provide realistic operators that will takes care of conversions for you, e.g., in your example, there is division operator between length and duration that gives speeds. The exact type of speed is automatically deduced from the type of the length and the type of duration:
auto speed = 70km / 1h; // Don't bother deducing the type of speed, let the compiler do it for you.
  • Unit types are self-documented: If a function returns microseconds, you know what this is, you do not have to hope that the guy documenting the function returning unsigned long long mentioned that this represents microseconds...

* I am only talking about implicit conversion here, of course, you can do conversion explicitly, e.g., using duration_cast (loss of precision).


Unit types

Wrapping integer types in "unit" classes has always been available, but C++11 brought one standard wrapped integer type: std::chrono::duration.

A "unit" class can be defined by:

  • the type of thing it represents: time, length, weight, speed, ...
  • the C++ type it uses to represent these data: int, double, ...
  • the ratio between this type and the "1-type" of the same unit.

Currently, only duration-like types are provided by the standard, but there have been discussion (maybe a proposal?) for providing a more generic basic unit type such as:

template <class Unit, class Rep, class Ratio = std::ratio<1>> class unit;

...where Unit would be a placeholder indicating the kind of thing represented, e.g.:

struct length_t { };

template <class Rep, class Ratio = std::ratio<1>>
using length = unit<length_t, Rep, Ratio>;

But this is not standard yet, so let's have a look at std::chrono::duration:

template <class Rep, class Period = std::ratio<1>> class duration;

The Rep template parameters is the C++ type:

  • Standard defined durations types have integer representations (implementation defined).
  • The underlying type defines the type of conversions that can be implicitly made:
    • You can convert integer hours to integer seconds implicitly (multiply by 3600).
    • You can convert integer seconds to integer hours implicitly because you would lose precision.
    • You can convert integer seconds to double hours.

The Period template parameters defines the ratio between the duration type and one second (which is the basic duration chosen):

  • std::ratio is a very convenient standard-defined type that simply represent a ratio between two integers, with corresponding operations (*, /, ...).
  • The standad provides multiple duration types with various ratios: std::chrono::seconds, std::chrono::minutes, ...

Operator literals

These have been introduced in C++11 and are literals operators.

The s one is standard and is included in the chrono standard library:

using namespace std::chrono_literals;
auto one_second = 1s;
auto one_hour = 1h;

The m one is not standard, and thus should be prefixed by _ (since it is user-defined), like 23_m. You can define your own operator like so:

constexpr auto operator "" _m(unsigned long long ull) { 
    return meters{ull};
}
like image 45
Holt Avatar answered Sep 23 '22 14:09

Holt