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?
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.
Types of Data Types in CFloating-point, integer, double, character. Derived Data Type. Union, structure, array, etc. Enumerated Data Type. Enums.
Char Size. The size of both unsigned and signed char is 1 byte always, irrespective of what compiler we use.
The size of int is usually 4 bytes (32 bits). And, it can take 232 distinct states from -2147483648 to 2147483647 .
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.
There are two different things involved in your code:
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).
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*:
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
).hours
to seconds
without having to manually multiply by 3600.auto speed = 70km / 1h; // Don't bother deducing the type of speed, let the compiler do it for you.
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:
int
, double
, ...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:
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 (*
, /
, ...).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};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With