一、继承
在c++编程中,一定是脱离不开继承的。而继承中有很多小的细节需要关注,比如如何显示的使用父类的构造函数?如何通过构造函数来推导模板参数等等。这些小的细节,其实都是在实际应用中对一些相关技术的不断完善。
二、继承的CTAD
在前面的《跟我学c++高级篇------c++17类模板实参推导CTAD》中,已经知道在c++17后放宽了对一些构造函数中的模板参数的显示指定而转为由编译器自动推导出来,而今天就要分析一下,在继承中的模板如何自动推导模板参数。
先回顾一下在c++17中的构造函数的推导:
c
//pre
std::mutex mt;
std::lock_guard lg{mt};
//c++17
std::lock_guard lg{mt};
那么,就可以顺理的推导到从继承可以自动推导相关的模板参数。其实很容易理解,子类当然可以调用父类的构造函数,不就直接转过去了么(有点类似于委托构造)。看一个例子:
c
struct A { A();A(int );... };
struct B : A
{
using A::A;
};
在更早期的版本中,可以使用一个完美转发来实现显示的对父类的构造。但这都有一些问题。比如上面的代码中如果构造函数传入的参数是一个Lambda表达式,即下面这种情况:
c
template <typename T >
struct A {
A(T&& t ):func_(t){}
T func_;
};
template <typename T >
struct B : A
{
using A<T>::A;
};
如果直接使用A a(<>{}),根据CTAD的推导,它是正确的构造过程。但如果使用B类也如此构造,则CTAD没法在这其中使用,结果当然是编译失败。当然,可以用一个auto类型存储一下这个表达式然后再decltype搞一下也能达到同样的目的,可似乎有点小麻烦了。而在C++23中则补齐了这个短板,直接可以使用了。即也可以B b{<>{}}进行初始化。
不过这不代表着它是完美的,举一个没有显示的构造函数情况下的例子就明白了:
c
struct A { char a,b,c; };
struct B : A
{
using A::A;
};
一般使用初始化列表来对其进行定义,即:A a{'a','a','b'};但对B b{'a','a','b'}仍然会报错,必须是模板参数推导,这都没参数了,推导个啥。
三、CTAD的例程
下面看一个简单的例程:
c
template<typename T>
struct A
{
A(T) {}
};
template<typename T>
struct B : public A<T>
{
using A<T>::A;
};
template<typename T>
struct C : public A<T>
{ };
B b{0}; // OK
C c(1); // ERR
A(int) -> A<char>; // 显示声明推导
B bb{2}; // OK,参数int被推导为char
是不是简单明了,标准对相关的CTAD的完善越来越多,以后应用起来,相对来说会简单不少。
四、总结
从目前看来,标准越是向前推进,相关的完善和填坑的动作也越多。那么相对来说,使用起来的通用性越好。而这也代表着编程相关对来说简单不少,毕竟少了不少的特异性的处理后,只要一般的应用即可。不用总想着这个特例那个特例。
简单才是王道!
c++23系列到这篇,基本就终止了。以后再写可能就是偶尔补充一下了。可能也就是c++26的新功能新属性了,变化才是永恒啊!