c++17 Class Template Argument Deduction(CTAD)
nxdong August 07, 2022 [cpp] #cppC++ How to Use Class Template Argument Deduction 部分学习和翻译
本文翻译自:how-to-use-class-template-argument-deduction
Class Template Argument Deduction (CTAD) 类模版参数推导是c++17的核心语言特性。
C++17的基础库也支持CTAD,升级之后可以在使用STL的的时候体验新特性,比如std::pair 或者 std::vector。
第三方库或者你自己的代码可以自动的从CTAD中获得便利,或者添加一点新代码(推断指引)来获取完整的好处。
幸运的是,使用CTAD或者提供推断指引是很容易的!
模版参数推导Template Argument Deduction
c++98 到c++14为函数模版提供了类型推导。
比如一个这样的函数模版:
void ;
你可以为std::vector<int>
排序而不用显式指定RanIt
是 std::vector<int>::iterator
。
当编译器看到sort(v.begin(), v.end());
时,它可以知道v.begin()
和 v.end()
的类型
,所以他可以决定RanIt
的类型。
然而,类模版不能从这些规则获益。 如果想用两个int
构造一个std::pair
, 一定要写成std::pair<int, int> p(11, 22);
这样,尽管编译器已经知道11
和 22
是int
类型。
一个变通的方法是使用函数模版的参数类型推导:std::make_pair(11, 22)
返回 std::pair<int, int>
. 跟大多数的权宜之计一样,这也有一些问题:
定义一个这样的辅助函数往往需要引入模版元编程(std::make_pair()
需要在其他的事情之外做完美转发与类型退化)。
降低了编译器的吞吐量(前端需要实例化,后端需要做优化)。
调试更复杂(需要跳进help函数)。
代码更冗杂(额外的 make_
前缀,如果需要局部变量,需要用auto)。
Hello, CTAD World
C++17 在只用类模版名字构造对象的扩展了模版参数类型推断。
现在可以写std::pair(11, 22)
,这个与std::pair<int, int>(11, 22)
相同。
meow.cpp 文件内容如下:
int
// clang++ --std=c++17 meow.cpp
CTAD在圆括号,大括号,有名变量,无名临时变量。
Another Example: array and greater
arr.cpp 内容如下:
using namespace std;
int
- CTAD 推断了
std::array
的元素类型和数量。 - CTAD 也在模版默认参数中生效。
greater{}
构造了一个greater<void>
的对象,因为他的声明是template <typename T = void> struct greater;
自定义类型的CTAD
mypair.cpp 文件的内容:
;
int
A 和 B 被推断为 int
but there are no constructor arguments and no default template arguments, so it can’t guess whether you want MyPair<int, int>
or MyPair<Starship,Captain>
.
Deduction Guides 推断指引
通常来说,CTAD在类模版的构造函数使用了所有类模版参数的时候自动生效。
然而,一些情况下构造函数自己就是模版,这个打断了CTAD所依赖的关系。
在这种情况下,类作者可以提供deduction guides 推导指引来告诉编译器如何从构造函数参数推断泪模版参数。
guides.cpp 文件内容如下:
;
-> ;
;
-> ;
int
// clang++ --std=c++17 ./guides.cpp
有两个常用的 STL deduction guide 案例: 迭代器和完美转发。 terators and perfect forwarding。
迭代器类型
MyVec
类似使用T作元素模版的 std::vector
, 但是它可以从迭代器类型 Iter
构造。
调用范围构造器提供了我们想要的类型信息,但是编译器不能确切的知道 Iter
和 T
之间的关系。
这就是deduction guide 起作用的地方。
在类模版定义之后,template <typename Iter> MyVec(Iter, Iter) -> MyVec<typename std::iterator_traits<Iter>::value_type>;
语法告诉编译器:在为MyVec
运行 CTAD的时候,尝试为MyVec(Iter, Iter)
签名做模版参数推导。如果成功匹配,想要构造的类型就是MyVec<typename std::iterator_traits<Iter>::value_type>
. 这个实际上就是从迭代器类型获取我们需要的类型。
完美转发
MyAdvancedPair
有一个跟 std::pair
一样的完美转发构造函数。编译器看A
和 B
与 T
和 U
相比,是不同的类型,而且也不知道他们之间的关系。在这情况下,我们想实现的转换是不同的:我们需要类型退化(decay)。
有趣的是,我们不需要decay_t
,尽管我们可以使用这个type trait(如果我们增加一些额外的代码)。
deduction guide template <typename X,typename Y> MyAdvancedPair(X, Y) -> MyAdvancedPair<X, Y>;
是足够的。 这句话告诉编译器:在为MyAdvancedPair
运行 CTAD的时候,尝试为MyAdvancedPair(X, Y)
签名做模版参数推导,如果这个是值传递,推导做类型退化(decay)。 如果匹配成功,你想要构造的类型就是MyAdvancedPair<X, Y>
。
Enforcement
在一些极端情况,你可能希望deduction guides 拒绝一些代码。
enforce.cpp 内容如下:
;
;
-> ;
int
// clang++ --std=c++17 ./enforce.cpp
MyArray
的类型参数推导指引 MyArray(First, Rest...)
强制所有的类型是相同的,并且推断出类型对象的数量。
Corner Cases for Experts: Non-Deduced Contexts
非推导类型:what-is-a-nondeduced-context
corner1.cpp 内容如下:
;
;
int
typename Identity<T>::type
阻止编译器推导 T
是 double
.
corner2.cpp 内容:
;
;
int
CTAD succeeds but constructor overload resolution fails. CTAD ignores the constructor taking (typename Identity<T>::type, unsigned long)
due to the non-deduced context, so CTAD uses only (T, long)
for deduction. Like any function template, comparing the parameters (T, long)
to the argument types double, int
deduces T
to be double
. (int is convertible to long
, which is sufficient for template argument deduction; it doesn’t demand an exact match there.) After CTAD has determined that Corner2<double>
should be constructed, constructor overload resolution considers both signatures (double,long)
and (double, unsigned long)
after substitution, and those are ambiguous for the argument types double, int
(because int
is convertible to both long
and unsigned long
, and the Standard doesn’t prefer either conversion).
Corner Cases for Experts: Deduction Guides Are Preferred
corner3.cpp 内容如下:
;
-> Corner3<X *>;
int
参考
https://devblogs.microsoft.com/cppblog/how-to-use-class-template-argument-deduction/
https://stackoverflow.com/questions/25245453/what-is-a-nondeduced-context