![现代C++编程实战:132个核心技巧示例(原书第2版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/258/50418258/b_50418258.jpg)
1.14.2 工作原理
在C++17之前,你必须在初始化变量时指定所有的模板参数,因为为了实例化类模板,所有的参数都必须是已知的,例如:
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/69_02.jpg?sign=1739698555-osesOSYWnhBzhNBmW8j0fh9D2nrABr7V-0-747b340a07881d743e5b6d2e7b9dac11)
使用函数模板(例如std::make_pair())可以避免显式指定模板参数的问题,这得益于函数模板参数的推导,允许我们编写如下代码:
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/69_03.jpg?sign=1739698555-DqiauoBlN1wwykkupuCUhWcQTeJaV7Ci-0-1f57fd32b93816a35e96268e8c12f143)
对于下面这个foo类模板,我们可以编写以下make_foo()函数模板来达到相同的效果:
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/69_04.jpg?sign=1739698555-KNmx4NKHr27eeFnXa1sBzjt46FOKn6Q3-0-fb07f0a73325d931463b495faab78299)
在C++17中,对于本小节中列举的示例,我们没有必要那样做。我们可以像下面这样编写代码:
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/69_05.jpg?sign=1739698555-zL6sRHJHJfMA4paXsLTYmJ5MPI5zpnwg-0-a823fd56f5da58196be6a662de0939fb)
在这个上下文中,std::pair不是类型,而是作为激活类模板参数推导的类型的占位符。当编译器在声明带有初始化的变量或函数样式强制转换过程中遇到它时,会构建一组推导引导。这些推导引导是假想类类型虚构的构造函数。作为用户,你可以用用户定义的推导规则来补充这个集合,这个集合用于执行模板参数推导和重载解析。
对于下面这个std::pair例子,编译器将构建一组推导引导,其中包括以下虚构的函数模板(但不仅限于这些):
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/69_06.jpg?sign=1739698555-KimdQKDOIRAjbpvdLxiv71UNDHJYKPWI-0-af3f489ef165b4e45c1dbedfcaa52df3)
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/70_01.jpg?sign=1739698555-3patgR3W9WzSqzrQQgxCBva60ERAto8g-0-c7690912e50a22ffd2cb41457a09ccd9)
这些编译器生成的推导引导是从类模板的构造函数创建的,如果没有,则从假想的默认构造函数创建推导引导。此外,在所有情况下,假想的复制构造函数总是会创建推导引导。
用户定义的推导引导是带有尾部返回类型且没有auto关键字的函数签名(因为它们表示没有返回值的假想构造函数),它们必须定义在它们应用的类模板命名空间中。
为了理解它的工作原理,我们考虑下面这个例子,同样以std::pair对象为例:
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/70_02.jpg?sign=1739698555-D5UDFGBj3bxINtC8FcbnUan5dttzS2Pj-0-aec2b327db39b21e3649d06f7f55b6a6)
编译器推导的类型是std::pair<int, char const*>,如果想让编译器推导std::string而不是char const*,需要用到几个用户定义的推导规则,如下所示:
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/70_03.jpg?sign=1739698555-IpArhumk78swF29uP15ie9o01ZyTwgMY-0-94fc0be5361aca6b0ad86e6e800cb782)
这样我们就可以编写以下声明,其中字符串"demo"的类型总是被推导为std::string:
从这个例子中可以看到,推导引导不一定是函数模板。
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/70_05.jpg?sign=1739698555-r069nUoJWeppOu7zZ6OOwmR5RdOWOAf7-0-6233b85805d57652b78da96ad2da76c8)
需要注意的是,无论指定的参数有多少,如果存在模板参数列表,则不会进行类模板参数推导。例如:
![](https://epubservercos.yuewen.com/4AD12D/29686922007544106/epubprivate/OEBPS/Images/70_06.jpg?sign=1739698555-VEQU5RerASBB1U2gwg5ot0gYIVgl7MG9-0-b1279f9378a3a34d854a2e83ce20d4a2)
因为这两个声明都指定了模板参数列表,所以它们是无效的,会导致编译错误。