type_identity
C++ 20 引入的 std::type_identity 是一个简单的类型萃取工具,用于阻止模板参数的类型推导。
例子
考虑如下的模板函数 invoke_callback:
#include <functional>
template <typename T>
void invoke_callback(std::function<void (T) callback, T value)
{
callback(value);
}
int main() {
invoke_callback([](int x) {}, 100);
}
编译上述代码会报错:
foo.cpp:13:5: error: no matching function for call to 'invoke_callback'
13 | invoke_callback([] (int) {}, 100);
| ^~~~~~~~~~~~~~~~
foo.cpp:6:6: note: candidate template ignored: could not match 'std::function<void (T)>' against '(lambda at foo.cpp:13:22)'
6 | void invoke_callback(std::function<void (T)> callback, T v)
| ^
1 error generated.
错误信息显示无法将 lambda 表达式匹配到 std::function<void (T)>。对于 100 这个参数,T 被推导为 int。于是,callback 的类型 std::function<void (T)> 实际上变成了 std::function<void (int)>。而编译器实际上拿到的 callback 类型是 lambda,与 std::function<void (int)> 并不匹配。
乍一看有些反直觉,因为下面的代码是完全合法的:
std::function<void (int)> func = [](int x) {};
这就涉及到模板编译过程中两条重要的规则:
- 规则 1:对于同样的模板参数
T,如果其被用作多个参数的类型,每个参数的类型推导是独立进行的。 - 规则 2:模板参数类型推导和隐式类型转换不能同时发生。
由于模板参数的推导总是最先进行的,因此编译器会尝试由 lambda 表达式的类型去推导 std::function<void (T)> 中 T,此时由 lambda 到 std::function 的转换并没有发生,造成推导失败。因此,不论 T 实际是什么类型,lambda 表达式都无法匹配 std::function<void (T)>。报错中的 std::function<void (int)> 看起来是编译器已经从 lambda 推导出来 T 的实际类型,但实际上这从参数 100 推导得出的。编译无法通过的原因在于违背了规则 2。
解决方法
显然,编译器是能够从传入的参数正确推导出 T 的实际类型的,那么我们只需要告知编译器直接使用已经推导出的类型,即阻止独立类型推导。
template <typename T>
void invoke_callback(std::function<void (std::type_identity_t<T>)> callback, T value)
{
callback(value);
}
int main()
{
invoke_callback([](int x) {}, 100);
}
这样,std::type_identity_t<T> 会直接使用已经推导出的 T 类型,这样,从 lambda 到 std::function<void (int)> 的隐式类型转换就可以顺利进行。
我们可以将上述示例推广到更通用的情形:
#include <iostream>
#include <functional>
template <typename... VA>
// void invoke_callback(std::function<void (VA&&... va)> callback, VA&&... va) // 错误
void invoke_callback(std::function<void (std::type_identity_t<VA>...)> callback, VA&&... va) // 正确
{
callback(std::forward<VA>(va)...);
}
int main()
{
invoke_callback([](int x, double y) {
std::cout << "x: " << x << ", y: " << y << std::endl;
}, 42, 3.14);
}
type_identity 实现
STL 中 std::type_identity 的实现非常简单,如下所示:
template <class T>
struct type_identity {
using type = T;
};
template <class T>
using type_identity_t = typename type_identity<T>::type;
其原理也很简单:type_identity 是一个模板结构体,其成员类型 type 就是模板参数 T 本身。通过 type_identity_t 别名模板,获取 type_identity 中的成员类型 type。
这样,std::type_identity_t<T> 实际上就是 T 本身,但由于其被包裹在 type_identity 结构体中,编译器不会对其进行独立的类型推导,从而阻止了规则 1 的发生。