Каков наилучший способ формирования пользовательских std::chrono::durations и std::ratios?

Я читал этот отличный ответ, в котором использовалась комичная единица времени microfortnights, чтобы проиллюстрировать хороший момент запоминающимся способом.

typedef std::ratio<756, 625> microfortnights;
std::chrono::duration<int, microfortnights> two_weeks(1000000);

И у меня возник вопрос:

Если бы я действительно хотел это сделать (скорее, какая-то другая нетривиальная продолжительность, такая как время, доступное в течение кадра или в течение N циклов процессора), как лучше всего это сделать?

Я знаю, что ratio<N, D> создаст уникальный тип, связанный с каждым значением N и D. Таким образом, ratio<4, 6> — это другой тип, чем ratio<2, 3>, даже если они представляют одну и ту же (уменьшенную) дробь. Всегда ли мне нужно заниматься математикой, чтобы упростить коэффициент преобразования в сокращенные условия?

Было бы удобнее написать:

using microfortnights = std::chrono::duration<long, ratio<86400*14, 1000000>>;

вместо:

using microfortnights = std::chrono::duration<long, ratio<756, 625>>;

Но тогда это были бы два разных типа, а не один и тот же тип. Правильность первого выражения легче проверить. Но есть много представлений этой дроби, и второе, возможно, является каноническим и, следовательно, предпочтительным. Если у меня слишком много типов, блуждающих по моей программе, которые на самом деле представляют один и тот же модуль, это может привести к ненужному раздуванию кода шаблона.


person Howard Hinnant    schedule 10.01.2015    source источник
comment
Можно ли вместо этого перефразировать заголовок вопроса о std::ratio? Кажется, что общая тема имеет очень мало общего с chrono, за исключением выбранного примера?   -  person sehe    schedule 10.01.2015
comment
У вас есть хорошая мысль. Однако я хочу сосредоточиться на durations, иначе я боюсь, что моя аудитория не увидит практического применения ratio. Я добавил ratio к заголовку, чтобы при поиске ratio было легче найти этот вопрос/ответ.   -  person Howard Hinnant    schedule 11.01.2015
comment
Хе-хе. +1 за набор в любом случае. (По иронии судьбы, мне потребовалось некоторое время, чтобы понять, что это на самом деле более актуально, чем просто создать неуловимое правильное микродвухнедельник :))   -  person sehe    schedule 11.01.2015
comment
Разве 2000000 микродней не four_weeks, а не two_weeks?   -  person Nemo    schedule 25.03.2015


Ответы (1)


Ниже я игнорирую пространства имен для краткости. duration находится в пространстве имен std::chrono, а ratio — в пространстве имен std.

Есть два хороших способа всегда гарантировать, что ваш ratio будет уменьшен до самых низких значений без необходимости выполнять арифметические действия самостоятельно. Первый довольно прямой:

Прямая формулировка

Если вы просто хотите сразу перейти к microfortnights, но не выясняя, что сокращенная дробь 86 400*14/1 000 000 равна 756/625, просто добавьте ::type после ratio:

using microfortnights = duration<long, ratio<86400*14, 1000000>::type>;

Вложенное type каждого ratio<N, D> — это еще одно ratio<Nr, Dr>, где Nr/Dr — сокращенная дробь N/D. Если N/D уже уменьшено, то ratio<N, D>::type имеет тот же тип, что и ratio<N, D>. В самом деле, если бы я уже понял, что 756/625 — правильная сокращенная дробь, но был бы просто параноиком, думая, что ее можно еще уменьшить, я мог бы написать:

using microfortnights = duration<long, ratio<756, 625>::type>;

Поэтому, если у вас есть какие-либо сомнения в том, что ваш ratio выражен в самых низких терминах, или вы просто не хотите возиться с проверкой, вы всегда можете добавить ::type к вашему типу ratio, чтобы быть уверенным.

Подробная формулировка

Пользовательские единицы продолжительности времени часто появляются как часть семейства. И часто удобно иметь всю семью доступной для вашего кода. Например, microfortnights очевидно связано с fortnights, которое, в свою очередь, связано с weeks, производным от days, производным от hours (или от seconds, если хотите).

Создавая свою семью по одному элементу за раз, вы не только делаете доступной всю семью, но и снижаете вероятность ошибок, связывая одного члена семьи с другим с помощью простейшего преобразования. Кроме того, использование std::ratio_multiply и std::ratio_divide вместо умножения литералов также означает, что вам не нужно постоянно вставлять ::type, чтобы гарантировать, что вы сохраняете ratio в наименьших выражениях.

Например:

using days = duration<long, ratio_multiply<hours::period, ratio<24>>>;

ratio_multiply – это имя-определения-типа, полученное в результате умножения, уже приведенного к наименьшим терминам. Таким образом, вышеприведенное является точно тем же типом, что и:

using days = duration<long, ratio<86400>>;

Вы даже можете иметь оба определения в одной и той же единице перевода, и вы не получите ошибку переопределения. В любом случае теперь вы можете сказать:

using weeks           = duration<long, ratio_multiply<days::period,       ratio<7>>>;
using fortnights      = duration<long, ratio_multiply<weeks::period,      ratio<2>>>;
using microfortnights = duration<long, ratio_multiply<fortnights::period, micro>>;

И мы получили имя_типа для microfortnights, которое является точно тем же типом, что и в нашей прямой формулировке, но с помощью ряда гораздо более простых преобразований. Нам по-прежнему не нужно возиться с сокращением дробей до наименьших членов, и теперь у нас есть несколько полезных единиц измерения вместо одной.

Также обратите внимание на использование std::micro вместо std::ratio<1, 1000000>. Это еще одно место, где можно избежать ошибок по невнимательности. Так легко (по крайней мере, для меня) напечатать (и неправильно прочитать) количество нулей.

person Howard Hinnant    schedule 10.01.2015