一、介绍和回顾
在前边的公众号中,两次介绍过自动推导(Class template argument deduction )即CTAD(类模板参数推导),它们分别是《c++类型推导的应用》和《c++类型识别和类型自动推导》,这两篇文章从实现和应用两个角度对类型的推导进行了分析和实际举例说明。基本上来说,自动推导的知识体系已经建立起来。
在c++17以前,正常情况下,使用类模板时,都应该显示的指出模板参数的类型(默认值除外),否则编译器会毫不留情的给你一个“复杂”的提示,让你一下子找不到北。但在从c++17后,这种方式有了明显的改观,如果在构造函数中可以推导出模板参数类型,那么就等于可以不用再显示指定模板参数类型了。
二、应用方式
1、隐式的推导
这种推导机制其实类似于模板函数中的参数的推导,它会根据c++定义推导规则来自动推导相关的数据类型。在c++17前和后可以这样使用:
std::mutex m;
std::recursive_timed_mutex rm;
void threadfunc1()
{
std::lock_guard<std::mutex> g{m};//pre C++17
std::lock_guard g{m};
}
void threadfunc2()
{
std::lock_guard<std::recursive_timed_mutex> g{rm};//pre C++17
std::lock_guard g{rm};
}
2、用户自定义的显示的推导
这种应用在前面的文章中介绍过,一些模板由开发者自己定义一个规则来帮助编译器来实现类型的推导,防止产生歧义导致编译错误。在前面的《泛型lambda表达式》就对这种自定义推导规则进行过说明,有兴趣可以去看一看。
template<typename T>
class Test {
public:
Test(T elem) {}
};
Test(char const*)->Test<std::string>;
用户自定义的推导规则形式如下:
explicit-specifier(optional) template-name ( parameter-declaration-clause ) -> simple-template-id ;
使用自定义显示推导需要注意一个问题,就是显示推导的定义导致了编译的通过,但编译通过后的代码无法继续编译,这种错误要区分出来。比如只有一个参数的构造函数,自定义推导定义了两个,那么产生的错误不是推导定义错误,是没有提供匹配的构造函数引起的。
3、别名推导
别名模板的推导其实如果理解什么是别名模板,就理解了。如果定义了一个别名A其真正的定义是B,那么,实际的推导还是按照1中的类模板推导方式进行。
三、例程
下面是一个用户显示指定推导规则的例子:
#include <iostream>
template <typename T>
class Example
{
private:
T t1;
T t2;
public:
Example() = default;
Example(T a1, T a2) : t1{ a1 }, t2{ a2 } {}
T Get() const { return t1 + t2; }
};
//此处需要注意,参数必须显示的一致,即char* != const char *
Example(const char*, const char*)->Example<std::string>;
int main()
{
Example<int> e1(3, 1);
Example e2(3, 2);
Example e3{ 2.1, 1.2 };
Example e4 = { 0.2f, 0.3f };
//对比下面两个
Example<std::string> e5("test", " ok");
Example e6("ok", " test");
return 0;
}
在上的的推导定义中,不需要必须是模板形式,这个需要注意。
下面看一下在c++20后提供的“非类型模板型参”的推导:
template<class T>
struct X
{
X(T) {}
auto operator<=>(const X&) const = default;
};
template<X x>
struct Y {};
Y<0> y; // OK,Y<X<int>(0)>
c++的自动推导规则不断的完善和简化,其实就是让c++的应用更简单一些。更多的一些知识可以在下面的网站学习:
https://zh.cppreference.com/w/cpp/language/class_template_argument_deduction
四、总结
c++标准的不断的迭代,也就必然导致一些新的标准与老的应用的之间的不匹配(这里不说冲突),那么导致的结果只有是老的被淘汰或者新旧兼容。但是,大家都知道,往往新旧兼容的成本是非常调的。特别是如果标准更迭快速时,这种兼容更是难以忍受的成本。所以如何引导这种标准的发延续性,其实就是一个非常大的难题。应用者往往感受不到这种难受或者说感受很轻,但制定标准的人就需要大费周章了。
而CTAD这种放宽原有的标准其实就是比较容易做的,等于打了个补丁,原来不可以的可以了。缩小范围不好缩,因为原来的可能无法使用了,编译器厂商会头疼,上层应用的也会头疼,但扩大范围好扩大,这也是设计原则里的开闭原则的一种变相应用。