为何三元运算符中两个std::chrono::duration会返回更高分辨率类型?
问题详情
当将两个不同分辨率的std::chrono::duration作为三元运算符的操作数时,返回值类型总是分辨率更高的那个,比如下面的代码:
#include <iostream> #include <chrono> #include <typeinfo> using namespace std; using namespace chrono; int main() { auto dur = true ? 0ms : 0s; cout << typeid(dur).name() << endl; }
在MSVC环境下输出为:
class std::chrono::duration<__int64,struct std::ratio<1,1000> >
也就是std::chrono::milliseconds类型。对于内置类型,编译器天然知道它们的公共类型,但duration作为标准库模板,编译器是如何确定这个公共类型的?
为了验证三元运算符是否依赖std::common_type,我写了一段测试代码:
#include <iostream> #include <type_traits> #include <typeinfo> using namespace std; struct X {}; struct A { operator X() { return X(); }; }; struct B { operator X() { return X(); }; }; template<> struct common_type<A, B> { using type = X; }; int main() { cout << typeid(common_type_t<A, B>).name() << endl; cout << typeid(true ? A() : B()).name() << endl; }
这段代码无法编译,编译器提示操作数类型不兼容,说明手动特化common_type对三元运算符的类型推导没有作用。那为什么不同的duration类型就能在三元运算符中正常工作?
解答
1. std::chrono::duration的特殊处理
C++标准为std::chrono::duration做了两项关键设计,让它能在三元运算符中正确推导类型:
- 特化的std::common_type:标准库专门为
duration特化了std::common_type模板,其推导规则是:取两个duration周期的最小公倍数对应的分数作为公共类型的周期(也就是分辨率更高的方向),同时选择能容纳两个数值类型的公共表示类型。比如seconds(周期1秒)和milliseconds(周期1/1000秒)的公共类型就是milliseconds,因为1秒是1000毫秒的整数倍,转换不会丢失精度。 - 隐式转换支持:
duration支持从低分辨率向高分辨率的隐式转换(比如seconds可以自动转成milliseconds),这满足了三元运算符对“两个操作数可转换为同一公共类型”的要求。
回到测试代码,0ms是std::chrono::duration<long long, std::milli>,0s是std::chrono::duration<long long, std::ratio<1>>,它们的common_type计算结果就是分辨率更高的milliseconds类型,所以三元运算符最终返回该类型。
2. 自定义类型失效的原因
为A、B特化common_type后依然无法通过编译,是因为C++三元运算符的类型推导规则并不直接依赖std::common_type:
- 三元运算符要求两个操作数要么可以互相隐式转换,要么都能隐式转换到同一个第三方类型,编译器才会将该类型作为返回类型。
std::common_type本身只是一个类型萃取工具,只有当语言特性或标准库明确规定要使用它时(比如duration的场景),编译器才会关联它和类型推导逻辑。A和B之间没有互相转换的能力,虽然都能转成X,但编译器不会主动把common_type的结果X作为中间转换类型来使用。
而std::chrono::duration是标准库类型,C++标准明确规定了它的common_type特化规则,同时配套了对应的隐式转换,因此编译器能正确推导出三元运算符的返回类型。
内容的提问来源于stack exchange,提问作者Edison von Myosotis

