如何无需显式Tm类直接构造std::chrono高分辨率时钟时间点?
嘿,这个需求很有意思!咱们来聊聊怎么实现这种简洁的time_point直接构造,以及你提到的几个思路~
首先得明确核心限制:你没法给标准库的std::chrono::time_point或其关联时钟添加新构造函数,而且编译器不会自动把裸初始化列表{2013,1,2,...}转换成你的自定义Tm类型(因为初始化列表是braced-init-list,没有明确类型,编译器不会触发隐式转换)。所以std::chrono::high_resolution_clock::time_point tp4{{2013, 1,2,10,11,12,-1, 1234}};这种写法确实没法直接生效,但咱们有几个替代方案可以达到类似的简洁性:
方案1:工厂函数(最接近你的原始写法)
既然不能直接扩展time_point,咱们可以写一个轻量的工厂函数,把构造Tm和转换time_point的逻辑封装起来,调用时完全不用显式写Tm:
#include <chrono> #include <ctime> // 先保留你的自定义Tm类(假设实现了到time_point的转换) struct Tm : std::tm { Tm(int year, int month, int day, int hour, int min, int sec, int dst, int usec) { tm_year = year - 1900; // std::tm年份是1900偏移量 tm_mon = month - 1; // 月份从0开始 tm_mday = day; tm_hour = hour; tm_min = min; tm_sec = sec; tm_isdst = dst; usec_ = usec; } operator std::chrono::high_resolution_clock::time_point() const { auto tmp_tm = *this; // mktime会修改tm,所以拷贝一份 auto time_t_val = mktime(&tmp_tm); auto sys_tp = std::chrono::system_clock::from_time_t(time_t_val); // 转换到high_resolution_clock(处理平台差异) auto high_tp = std::chrono::time_point_cast<std::chrono::high_resolution_clock::duration>(sys_tp); return high_tp + std::chrono::microseconds(usec_); } private: int usec_; }; // 工厂函数:接收时间参数,内部构造Tm并转换为time_point auto make_high_res_time_point(int year, int month, int day, int hour, int min, int sec, int dst, int usec) { return static_cast<std::chrono::high_resolution_clock::time_point>( Tm{year, month, day, hour, min, sec, dst, usec} ); } // 使用方式(和你想要的写法几乎一样,只是多了函数名) int main() { auto tp4 = make_high_res_time_point(2013, 1, 2, 10, 11, 12, -1, 1234); }
这个方案的优势是完全贴合你的原始需求,代码改动小,调用简洁,而且不需要额外学习新语法。
方案2:用户定义字面量(Howard提到的思路)
字面量的方式可读性更强,而且可以用更自然的语法来表示时间。这里分两种实现思路:
思路A:字符串字面量(推荐,直观不易错)
直接用标准的日期时间字符串加后缀,内部解析成time_point:
#include <chrono> #include <ctime> #include <string> #include <sstream> #include <iomanip> // 自定义字面量后缀_tp,解析日期时间字符串为high_resolution_clock::time_point std::chrono::high_resolution_clock::time_point operator""_tp(const char* str, std::size_t len) { std::string time_str(str, len); std::tm tm{}; int usec = 0; std::istringstream iss(time_str); // 解析日期和时间部分(格式:YYYY-MM-DD HH:MM:SS.uuuuuu) iss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); char dot; iss >> dot >> usec; // 转换为time_point auto tmp_tm = tm; auto time_t_val = mktime(&tmp_tm); auto sys_tp = std::chrono::system_clock::from_time_t(time_t_val); auto high_tp = std::chrono::time_point_cast<std::chrono::high_resolution_clock::duration>(sys_tp); return high_tp + std::chrono::microseconds(usec); } // 使用方式 int main() { auto tp4 = "2013-01-02 10:11:12.001234"_tp; }
这种写法非常直观,符合日常书写日期时间的习惯,而且可以轻松扩展支持其他格式(比如带时区的字符串)。
思路B:链式字面量(接近你的参数列表风格)
通过重载/运算符,把年份、月份等参数链式组合起来:
#include <chrono> #include <ctime> // 辅助结构体,存储日期和时间部分 struct DateComponents { int year; int month; int day; }; struct TimeComponents { int hour; int min; int sec; int usec; int dst; }; // 年份字面量 DateComponents operator""_y(unsigned long long year) { return {static_cast<int>(year), 0, 0}; } // 链式组合月份、日期 DateComponents operator/(DateComponents d, unsigned long long month) { d.month = static_cast<int>(month); return d; } DateComponents operator/(DateComponents d, unsigned long long day) { d.day = static_cast<int>(day); return d; } // 链式组合小时、分钟、秒、微秒 TimeComponents operator/(DateComponents d, unsigned long long hour) { return {static_cast<int>(hour), 0, 0, 0, -1}; // 默认dst=-1 } TimeComponents operator/(TimeComponents t, unsigned long long min) { t.min = static_cast<int>(min); return t; } TimeComponents operator/(TimeComponents t, unsigned long long sec) { t.sec = static_cast<int>(sec); return t; } TimeComponents operator/(TimeComponents t, unsigned long long usec) { t.usec = static_cast<int>(usec); return t; } // 最终转换为time_point std::chrono::high_resolution_clock::time_point operator/(DateComponents d, TimeComponents t) { Tm tm{d.year, d.month, d.day, t.hour, t.min, t.sec, t.dst, t.usec}; return static_cast<std::chrono::high_resolution_clock::time_point>(tm); } // 使用方式 int main() { auto tp4 = 2013_y / 1 / 2 / 10 / 11 / 12 / 1234; }
这种写法完全模拟了你想要的参数列表风格,通过字面量和运算符重载实现了“自然语言”式的时间构造。
关于用户定义推导指引的补充
你提到的推导指引其实不太适用这里:推导指引是用来帮助编译器推导模板类的模板参数的,而std::chrono::high_resolution_clock::time_point的模板参数(时钟、duration)都是固定的,推导指引没法帮你添加新的构造逻辑。所以推导指引并不能让time_point{2013,1,2,...}生效,还是得靠上面的工厂函数或字面量方案。
内容的提问来源于stack exchange,提问作者Klaus

