《Effective Modern C++》条款3

条款3:理解decltype(Understand decltype)

decltype可以告诉你一个变量或一个表达式的类型。

一般情况下,decltype返回的类型和你所给变量或表达式的类型一模一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const int i = 0;          // decltype(i) is const int
bool f(const Widget& w); // decltype(w) is const Widget&
// decltype(f) is bool(const Widget&)

struct Point {
int x, y;
}; // decltype(Point::x) is int
// decltype(Point::y) is int

Widget w; // decltype(w) is Widget

if(f(w)) ... // decltype(f(w)) is bool


template<typename T> // simplified version of std::vector
class vector {
public:
...
T& operator[](std::size_t index);
...
};

vector<int> v; // decltype(v) is vector<int>
...
if (v[0] == 0) ... // decltype(v[0]) is int&

C++11中,decltype主要被用在那些其函数返回值依赖于其参数类型的函数模板声明中。

1
2
3
4
5
6
7
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
-> decltype(c[i])
{

authenticateUser();
return c[i];
}

C++14中,所有的lambda和函数的返回类型都可以被推导(C++11只支持单语句的lambda的返回类型推导)。因此,在C++14中,上例可以被写为:

1
2
3
4
5
6
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{

authenticateUser();
return c[i]; // 返回类型从c[i]中推导
}

条款2中说到,对函数返回值中的auto,编译器采用模板类型推导。对于大多数的容器来说,c[i]的类型为T&,但根据条款1中所述,在模板类型推导过程中,初始化表达式的常量性(&)被忽略,则本例中authAndAccess函数的返回值就为int,而不是所期望的int&

为了使得authAndAccess函数所我们期望的一样,在C++14中,我们需要使用decltype类型推导来推导出其返回值类型。

1
2
3
4
5
6
7
template<typename Container, typename Index>
decltype(auto)
authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}

decltype(auto)含义:
auto指示类型需要被推导,decltype指示在类型推导过程中使用decltype的规则。

当你在初始化表达式中想要使用decltype推导规则时,可以使用decltype(auto)来声明变量。

1
2
3
4
5
6
Widget w;
const Widget& cw = w;

auto myWidget1 = cw; // auto类型推导:myWidget1的类型为Widget

decltype(auto) myWidget2 = cw; // decltype类型推导:myWidget2的类型为const Widget&

再来看一下,authAndAccess函数在C++14中的声明:

1
2
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);

该函数只接受左值,而不接受右值(因为,右值不能绑定到一个非常量左值引用:Container&)。为了让该函数既接受左值,又接受右值,一个方法是重载该函数(一个重载版本声明为左值引用参数,另一个重载版本声明为右值引用参数),但是这带来的缺点是:需要维护两个函数。另一种避免出现这种情况的方式是:使用万能引用,见条款24

1
2
3
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c,
Index i);

然而,根据条款25,需要在万能引用上使用std::forward

1
2
3
4
5
6
7
template<typename Container, typename Index>
decltype(auto)
authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i];
}

同理,对于C++11,也要如此:

1
2
3
4
5
6
7
8
template<typename Container, typename Index>
auto
authAndAccess(Container&& c, Index i)
-> decltype(std::forward<Container>(c)[i])
{

authenticateUser();
return std::forward<Container>(c)[i];
}

对于类型为T的左值表达式,decltype返回T&

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int x = 0;  // decltype(x) is int, decltype((x)) is int&

decltype(auto) f1()
{
int x = 0;
...
return x; // decltype(x) is int, so f1 return int
}

decltype(auto) f2()
{
int x = 0;
...
return (x); // decltype((x)) is int&, so f1 return int&
}

f2返回值是int&,而且是局部变量的引用!小心该坑

要点:

  • decltype几乎总是产生和变量或表达式一模一样的类型
  • 对于类型为T的左值表达式,decltype返回的类型总是T&
  • C++14支持decltype(auto),像auto一样,从初始化对象中推导出类型,但是它使用decltype的规则进行推导

评论