3

C++ 模板系列小结07-尾置返回类型

 2 years ago
source link: https://glumes.com/post/c++/c++-template-7/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
C++ 模板系列小结07-尾置返回类型

在使用模板时可以显示指定模板类型,尤其是针对有返回类型的模板,显示指定可以避免类型转换带来的困扰。

但有时候显示指定模板实参类型会给用户增添额外负担,而且不会带来什么好处。

比如如下代码,接受表示序列的一对迭代器和返回序列中的一个元素的引用:

template<typename It>
??? &fcn(It beg,It end){
    return *beg;
}

我们并不知道返回结果的准确类型,但知道所需类型是所处理的序列的元素类型。

vector<int> vi = {1,2,3,4,5};
auto &i =fcn(vi.begin,vi.end());

如上代码,知道函数应该返回 *beg,而且知道我们可以用 decltype(*beg) 来获取此表达式的类型。

但是,在编译器遇到函数的参数列表之前,beg 都是不存在的。为此,我们需要使用尾置返回类型

由于尾置返回出现在参数列表之后,它可以使用函数的参数:

// 尾置返回允许在参数列表之后声明返回类型
template<typename It>
auto fcn(It beg,It end) -> decltype(*beg){
    return *beg;
}

在上面的例子中,fcn 的返回类型和解引用 beg 参数的结果类型相同。

解引用运算符返回一个左值,因此通过 decltype 推断的类型为 beg 表示的元素的类型的引用。

比如,如果对一个 string 序列调用 fcn,返回类型是 string& ,如果是 int 序列,返回类型是 int& 。

关于为何返回的类型是引用类型,可以回顾一下 decltype 的使用:

decltype 的使用

当希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。为了满足这一要求,C++11 新标准引入了类型说明符 decltype ,它的作用是选择并返回操作数的数据类型。

在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

int f(){
    return 1;
}

int main(){
    int x = 2;
    decltype(f()) sum = x;
    return 0;
}

如上代码所示,编译器并不实际调用函数 f ,而是使用当调用发生时 f 的返回值类型作为 sum 的类型。换句话说,编译器为 sum 指定的类型就是 f 被调用时返回的那个类型。

如果 decltype 使用的表达式是一个变量,则 decltype 返回该变量的类型(包括顶层 const 和引用在内)。

const int ci = 0, &cj = ci;
decltype(ci) x = 0;         // x 的类型是 const int
decltype(cj) y = x;         // y 的类型是 const int & ,y 绑定到变量 x
decltype(cj) z;             // z 是一个引用,必须初始化

如果 decltype 返回的变量类型是一个引用,那么对应的变量就必须初始化才行。

另外,如果 decltype 使用的表达式不是一个变量,则 decltype 返回表达式结果对应的类型。

有的表达式将向 decltype 返回一个引用类型。一般来说当这种情况发生时,意味着该表达式的结果对象能作为一条赋值语句的左值。

int i = 42,*p = &i, &r = i;
decltype(r + 0) b;      //正确:加法的结果是 int,因此 b 是一个未初始化的 int
decltype(*p) c;         //错误:c 是 int&,必须初始化

因为 r 是一个引用,因此 decltype(r) 必然是引用类型。如果想让结果是 r 所指的类型,可以把 r 作为表达式的一部分,如 r+0 ,显然这个表达式的结果将是一个具体指而非一个引用。

另外,如果一个表达式的内容是解引用操作,则 decltype 将得到引用类型。因为,解引用指针可以得到指针所指的对象,而且还能给这个对象赋值。因此,decltype(*p) 的结果就是 int& ,而非 int 。

另外,对于 decltype 所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。

如果 decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型。如果给变量加上了一层或多层括号,编译器会把它当成一个表达式,而变量是一种可以作为赋值语句左值的特殊表达式,这是 decltype 就会得到引用类型。

decltype((i)) d;  // 错误,d 是引用类型, int& ,必须要初始化
decltype(i) e;      //正确,e 是未初始化的 int 

尾置返回元素为值而非引用

在前面的例子中,返回的是引用类型,但是有时候希望返回一个元素的值,而非引用。

template<typename It>
auto fcn(It beg,It end) -> decltype(*beg){
    return *beg;
}

但问题在于,无法知道传递的参数类型,唯一可以使用的操作是迭代器操作,而所有迭代器操作都不会生成元素,只能生成元素的引用。

为了获得元素类型,要使用标准库的类型转换模板

这些模板定义在头文件 type_traits 中,主要是用于模板元编程设计的,这次主要用到 remove_reference 模板,它的源码如下:

template <class _Tp> struct _LIBCPP_TEMPLATE_VIS remove_reference        {typedef _LIBCPP_NODEBUG_TYPE _Tp type;};

如同它的名字一样,就是移除引用,可以通过它来获得元素类型。remove_reference 模板有一个模板类型参数和一个名为 type 的类型成员。

如果用一个引用类型实例化 remove_reference,则 type 将表示被引用的类型。

例如,实例化 remove_reference<int&> ,则 type 成员将是 int 。类似,如果实例化 remove_reference<string&> ,则 type 成员将是 string 。

如果给定一个迭代器 beg:

remove_reference<decltype(*beg)>::type

将获得 beg 引用的元素的类型:decltype(*beg) 返回元素类型的引用类型,remove_reference::type 脱去引用,剩下元素类型本身。

组合使用 remove_reference、尾置返回以及 decltype ,就可以在函数中返回元素值的拷贝。

template<typename It>
auto fcn2(It beg,It end) -> typename remove_reference<decltype(*beg)>::type{
    return *beg; // 返回序列中一个元素的拷贝
}

要注意的是,type 是一个类的成员,而该类依赖于一个模板参数。因此,要在返回类型的声明中使用 typename 来告知编译器,type 表示一个类型,关于这个知识点在之前的文章中提到过了,参见:C++ 模板系列小结03-在模板中指定变量类型

尾置返回类型主要还是 decltype 的使用了,而 decltype 要注意的是返回的类型是元素的类型,还是引用类型了,在不同场景下注意区分。

知识星球与微信公众号

扫描下面的二维码关注我的微信公众号《音视频开发进阶》,推送更多精彩内容!

欢迎加入 知识星球《音视频与图形图像的那些事》,还有更多的互动回答和知识分享再等你!

添加我的微信 ezglumes 拉你入群一起交流学习~

wechat-account-qrcode

原创文章,转载请注明来源:    C++ 模板系列小结07-尾置返回类型

「真诚赞赏,手留余香」


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK