尾置返回类型

尾置返回类型(trailing return type)是C++11中新增的特性,任何函数的定义都可以使用尾置返回类型,但是尾置返回类型更适合用于返回类型比较复杂的场景,如返回一个数组指针。下面的例子是返回一个指向维度为10的数组指针的函数定义方法:

1
int (*func(int i))[10]

下面逐层理解上述例子的含义:

  • func(int i)表示调用函数时,需要一个int类型的参数;
  • (*func(int i))表示对调用func的结果执行解引用的操作;
  • (*func(int i))[10]表示解引用之后得到一个维度为10的数组;
  • int (*func(int i))[10]表示数组的数据类型为int;

若使用尾置返回类型,上述函数的定义可以写成:

1
auto func(int i)->int (*)[10]

使用尾置返回类型之后,函数的定义更加清晰易懂;同时注意到,尾置类型通常要和auto结合使用

尾置lambda的返回类型

默认情况下,如果一个lambda中包含一个return之外的任何语句,编译器将假定此lambda的返回void。如下面的例子将vector中的负数转成正数:

1
2
std::vector<int> v {1, -9, 8, -3, 5};
transform(v.begin(), v.end(), v.begin(), [](int i){return i<0 : -i : i;});

上面的例子可以正常编译通过,若修改成if/else的形式这编译无法通过:

1
2
3
4
std::vector<int> v {1, -9, 8, -3, 5};
transform(v.begin(), v.end(), v.begin(), [](int i){
if (i<0) return -i; else return i;
});

下面的这段描述来自 《C++ Primer》(第5版),我分别在gcc4.8.5和gcc5.4.0的环境上测试了,都无编译错误,也许高版本的编译器对此有优化。感谢boringcat的指正。

此时lambda表达式默认返回类型为void,而return了一个int,因此无法编译通过。上述的例子可以通过尾置返回类型来指定具体的返回类型,如:

1
2
3
4
std::vector<int> v {1, -9, 8, -3, 5};
transform(v.begin(), v.end(), v.begin(), [](int i) -> int {
if (i<0) return -i; else return i;
});

尾置模板的返回类型

模板函数定义和调用如下:

1
2
3
4
5
6
7
8
9
template<typename R, typename T, typename U>
R add(T t, U u)
{
return t+u;
}

int a =1;
float b = 2.0;
auto c = add<decltype(a+b)>(a, b);

问题:add函数的返回类型能否通过decltype获取呢?

1
2
3
4
5
template<typename R, typename T, typename U>
decltype(t+u) add(T t, U u) // error: t、u尚未定义
{
return t+u;
}

上述在使用decltype(t+u)时,t、u尚未定义,编译失败;

问题:能否通过decltype(T()+U())推导呢?不一定可以,因为T、U可能不包含无参数的构造函数。但可以按照下面方式修改:
decltype( (*(T*)0) + (*(U*)0) )
虽然上述方式可以解决问题,但是写法很晦涩,通过auto、decltype和尾置返回类型,可以清晰的写出上述的逻辑。

1
2
3
4
5
template<typename R, typename T, typename U>
auto add(T t, U u) ->decltype(u+t)
{
return t+u;
}

一个函数wrapper

1
2
3
4
5
6
7
8
int& func(int& i );
float func(int& f);

template<typename T>
auto func(T& val) -> decltype(func(val))
{
return func(val);
}

尾置返回类型和auto、decltype的结合,可以有效的解决函数返回类型依赖于参数推导来确定的问题。