Единицы для типов в С++
В C++ Core Guidlines P.1 change_speed
пример показывает тип Speed
который используется, как показано ниже:
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
Меня особенно интересуют последние две строки этого примера. Первое, кажется, предполагает, что если вы не предоставите единицы с аргументом change_speed
это вызовет ошибку. В последней строке показаны единицы, определенные с использованием некоторых букв m
и s
. Являются ли обе эти новые функции в современных версиях C++? Если да, то как это будет реализовано, и какая версия C++ требуется?
Ответы
Ответ 1
Как отмечалось в комментариях, в примере из основных руководств используются пользовательские литералы для создания типов конкретных приложений, которые интуитивно представляют физические величины. Чтобы проиллюстрировать их для конкретного примера, рассмотрите следующие типы:
/* "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;
};
Вероятно, нет смысла отслеживать единицы объектов Length
, но не для экземпляров Speed
, но рассмотрим здесь самый простой пример. Теперь рассмотрим два пользовательских литерала:
#include <ratio>
auto operator ""_m(unsigned long long n)
{
return Length<>{n};
}
auto operator ""_km(unsigned long long n)
{
return Length<std::kilo>{n};
}
Они позволяют вам создавать объекты Length
следующим образом:
/* We use auto here, because the suffix is so crystal clear: */
const auto lengthInMeter = 23_m;
const auto lengthInKilometer = 23_km;
Чтобы сопоставить экземпляр Speed
, задайте подходящий оператор для деления Length
на 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()};
}
Теперь рассмотрим пример из основных принципов снова,
void change_speed(const Speed& s)
{
/* Complicated stuff... */
}
но самое главное, как вы можете назвать такую функцию:
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;
}
Как упоминалось в комментариях @KillzoneKid, для этого требуется С++ 11.
Ответ 2
В вашем коде есть две разные вещи:
-
Использование сильных/единичных типов, чтобы сделать ваш код более надежным, т.е. Вы дифференцируете два целочисленных типа. Это встроено на некоторых языках (например, Ada), но не в C++, но вы можете создавать классы, которые обертывают целочисленные типы, чтобы имитировать такое поведение (см. Ниже).
-
Использование операторных литералов для создания экземпляров таких классов по-дружески, т.е. Вы пишете 1s
вместо seconds{1}
. Это просто удобная функция, которая может быть полезна в некоторых местах.
Использование сильных целых типов очень полезно, потому что это делает ваш код гораздо менее подверженным ошибкам *:
- Вы не можете конвертировать между типами, представляющими длительности и длины, как в реальной жизни.
- Внутри типов, представляющих один и тот же тип (например,
seconds
и hours
), если вы теряете точность, нет неявных преобразований, например, вы не можете конвертировать seconds
в hours
, если вы не представляете наш тип с плавающей точкой (float
/double
). - (Неявные и неявные) конверсии позаботятся о масштабировании для вас: вы можете конвертировать
hours
в seconds
без необходимости вручную умножать на 3600. - Вы можете предоставить реалистичных операторов, которые позаботятся о конверсиях для вас, например, в вашем примере существует оператор разделения между длиной и продолжительностью, что дает скорости. Точный тип скорости автоматически выводится из типа длины и типа продолжительности:
auto speed = 70km / 1h; // Don't bother deducing the type of speed, let the compiler do it for you.
- Типы модулей самодокументированы: если функция возвращает
microseconds
, вы знаете, что это такое, вам не нужно надеяться, что парень, документирующий функцию, возвращающую unsigned long long
отметил, что это представляет микросекунды...
* Я говорю только о неявном преобразовании здесь, конечно, вы можете сделать преобразование явно, например, используя duration_cast
(потеря точности).
Типы единиц
Обнаружение целочисленных типов в классах "единиц" всегда было доступно, но C++ 11 принес один стандартный завернутый целочисленный тип: std::chrono::duration
.
Класс "единица" может быть определен:
- тип вещи, которую он представляет: время, длина, вес, скорость,...
- тип C++, который он использует для представления этих данных:
int
, double
,... - соотношение между этим типом и "1-типом" одной и той же единицы.
В настоящее время стандартными являются только типы, подобные длительности, но обсуждались (возможно, предложение?) Для обеспечения более общего базового типа единицы, например:
template <class Unit, class Rep, class Ratio = std::ratio<1>> class unit;
... где Unit
будет заполнителем, указывающим вид представленной вещи, например:
struct length_t { };
template <class Rep, class Ratio = std::ratio<1>>
using length = unit<length_t, Rep, Ratio>;
Но это еще не стандарт, поэтому давайте посмотрим на std::chrono::duration
:
template <class Rep, class Period = std::ratio<1>> class duration;
Параметрами шаблона Rep
являются тип C++:
- Стандартные определенные типы продолжительности имеют целочисленные представления (определенная реализация).
- Основной тип определяет тип конверсий, которые могут быть сделаны неявно:
- Вы можете мгновенно преобразовать целые часы в целые секунды (умножить на 3600).
- Вы можете конвертировать целые секунды в целые часы неявно, потому что вы потеряете точность.
- Вы можете преобразовать целые секунды в
double
часа.
Параметры шаблона Period
определяют отношение между duration
и одной секундой (которая выбирается основной базой):
-
std::ratio
- очень удобный стандартный тип, который просто представляет соотношение между двумя целыми числами с соответствующими операциями (*
, /
,...). - Стенд предоставляет несколько типов продолжительности с различными коэффициентами:
std::chrono::seconds
, std::chrono::minutes
,...
Операторные литералы
Они были введены в C++ 11 и являются операторами литералов.
Стандарт s
является стандартным и включен в стандартную библиотеку chrono
:
using namespace std::chrono_literals;
auto one_second = 1s;
auto one_hour = 1h;
m
не является стандартным, и поэтому его следует префикс _
(поскольку он определяется пользователем), например 23_m
. Вы можете определить свой собственный оператор следующим образом:
constexpr auto operator "" _m(unsigned long long ull) {
return meters{ull};
}