Единицы для типов в С++

В 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

В вашем коде есть две разные вещи:

  1. Использование сильных/единичных типов, чтобы сделать ваш код более надежным, т.е. Вы дифференцируете два целочисленных типа. Это встроено на некоторых языках (например, Ada), но не в C++, но вы можете создавать классы, которые обертывают целочисленные типы, чтобы имитировать такое поведение (см. Ниже).

  2. Использование операторных литералов для создания экземпляров таких классов по-дружески, т.е. Вы пишете 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};
}