是否可以超载 std::chrono::duration_cast?

is it possible to overload std::chrono::duration_cast?

我有一个设备可以将时间报告为时钟滴答中的整数秒和小数秒。对于这个特定的设备,时钟运行 256MHz。我已经定义了我在持续时间和 time_points:

中使用的自定义分辨率
using Res = std::chrono::duration<uint64_t, std::ratio<1, 256'000'000>>;

(除非相关,否则我宁愿不争论 uint 还是 int)。

此分辨率足以表示自 1970 年以来的时间,在 2763 左右溢出。

给定,

#include <iostream>
#include <chrono>

int main() {
    using Res = std::chrono::duration<uint64_t, std::ratio<1, 256'000'000>>;
    using namespace std::chrono;
    using TimePoint = time_point<system_clock, Res>;

    auto s_now = system_clock::now();

    auto simple = time_point_cast<Res>(s_now);

    auto s_sec = duration_cast<seconds>(s_now.time_since_epoch());
    auto hard = TimePoint(Res(s_sec));
    hard += duration_cast<Res>(s_now.time_since_epoch() - s_sec);

    std::cout << "s_now  " << s_now.time_since_epoch().count() << "\t" << s_sec.count() << "\n"
              << "simple " << simple.time_since_epoch().count() << "\t" << duration_cast<seconds>(simple.time_since_epoch()).count() << "\n"
              << "hard   " << hard.time_since_epoch().count()  << "\t" << duration_cast<seconds>(hard.time_since_epoch()).count() << "\n"
;
    return 0;
}

simple不等于hard。输出可能是这样的:

s_now  1628286679505051812  1628286679
simple 121693484773940438   475365174
hard   416841389953293263   1628286679

发生的事情是 time_point_cast int 代表 system_clock::duration

根据看来,这似乎是合法和道德的,但味道不太好。这个限制太容易产生bug了。

如果我有一种方法可以定义转换为我的非常高分辨率类型的实现,以防止溢出,那将是理想的。知道使用自定义辅助函数而不是强制转换只是自找麻烦,因为有人会忘记一些时间,而错误可能会在生产中被忽视。

我同意你点击溢出。但这不是因为 system_clock::repint。实际上,您的 system_clock::repint64_t,是的,它确实会溢出。

你的 system_clock::periodnano。而将nano转换为std::ratio<1, 256'000'000>的换算系数为32/125。并且 32 * 1628286679505051812 溢出了常见的 rep : uint64_t.

有几种方法可以避免溢出并得到正确答案。然后你展示其中一个。另一种方法是使用 128 位算法,它在您的平台上可用:

using D = duration<__int128_t>;
auto s_now = system_clock::now() + D{0};  // s_now::rep is now 128 bits

这会将输出更改为:

s_now  1628286679505051812  1628286679
simple 416841389953293263   1628286679
hard   416841389953293263   1628286679

最后,回答你的问题:不,不要覆盖 duration_cast。而是创建 your_cast 并用它做任何你想做的事。 C++17 已经通过引入 duration_cast/time_point_cast 的三种新“风味”来实现这一点,它们只是改变了默认的舍入模式:

template<class ToDuration, class Rep, class Period>
  constexpr ToDuration floor(const duration<Rep, Period>& d);
template<class ToDuration, class Rep, class Period>
  constexpr ToDuration ceil(const duration<Rep, Period>& d);
template<class ToDuration, class Rep, class Period>
  constexpr ToDuration round(const duration<Rep, Period>& d);

template<class ToDuration, class Clock, class Duration>
  constexpr time_point<Clock, ToDuration> floor(const time_point<Clock, Duration>& tp);
template<class ToDuration, class Clock, class Duration>
  constexpr time_point<Clock, ToDuration> ceil(const time_point<Clock, Duration>& tp);
template<class ToDuration, class Clock, class Duration>
  constexpr time_point<Clock, ToDuration> round(const time_point<Clock, Duration>& tp);

Here 是这些函数的示例实现。只需复制一个,为 your_cast 选择一个合适的名称,然后以最适合您的方式实施它。