探究fmt库中format_string的模板参数推导机制
当你调用fmt::print("Hello, {}", a);时,整个模板参数推导的核心逻辑是先从print函数的第二个参数包推导类型,再传递给第一个参数的format_string,具体步骤如下:
1. print函数的模板参数推导
print的签名是:
template <typename... T> FMT_INLINE void print(format_string<T...> fmt, T&&... args);
编译器首先处理第二个参数args(也就是例子中的a),推导出模板参数包T...的具体类型。比如如果a是int类型,那么T...就是int。
2. format_string的类型传递
format_string<T...>是一个类型别名:
template <typename... Args> using format_string = basic_format_string<char, type_identity_t<Args>...>;
这里type_identity_t<Args>的作用是保留原类型,避免模板推导时的类型折叠(比如防止右值引用被推导为左值)。当print的T...确定后,format_string<T...>就被实例化为basic_format_string<char, type_identity_t<T>...>,此时basic_format_string的模板参数Char固定为char,Args...就是print推导出来的T...。
3. basic_format_string的构造无需推导模板参数
此时basic_format_string的模板参数Char和Args...已经完全确定,不需要从构造函数推导。构造函数:
template <typename S, FMT_ENABLE_IF( std::is_convertible<const S&, basic_string_view<Char>>::value)> FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) { // 编译期检查逻辑 }
这里的模板参数S是用来匹配传入的字符串类型(比如字符串字面量、std::string等),只要它能转换为basic_string_view<Char>就可以。而basic_format_string自身的Args...已经由print函数传递过来,构造函数会用这个Args...来做编译期的格式字符串校验(比如检查{}的数量和类型是否匹配参数)。
为什么不需要用户定义推导指南?
因为basic_format_string的模板参数不是从构造函数推导的,而是由上层的print函数预先确定好传递进来的。推导指南是用来让编译器从构造函数参数推导类模板参数的,但这里根本不需要——类模板参数已经由外部上下文确定了,构造函数只需要处理字符串参数的类型匹配即可。
内容的提问来源于stack exchange,提问作者RnMss

