- 作用:给出一个变量名或表达式,
decltype 返回变量或表达式的类型。偶尔,decltype 返回的结果会让你非常迷惑。 - 先从正常的例子看起。不像模板和
auto 的类型推导,decltype 一般返回的就是准确的类型:
const int i = 0;
bool f(const Widget& w);
struct Point {
int x, y;
};
Widget w;
if (f(w))
vector<int> v;
if (v[0] == 0)
- 在C++11中,
decltype 的最初用途也许是声明返回值依赖于参数类型的函数模板。例如我们要写一个函数,该函数接受一个容器变量c和一个索引i,函数中先调用某个其它函数,然后返回c[i]。此时函数返回值类型取决于容器元素的类型,使用 decltype 可以轻松地表示出该语义:
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i])
{
authenticateUser();
return c[i];
}
- 这里使用的
auto 与类型推导无关,而是C++11的一种语法:返回值类型后置(trailing return type),即声明把函数返回值类型放到参数列表后面,这使得我们可以使用参数列表中的符号声明返回类型。本例中,我们结合该语法和 decltype 实现了把返回值类型定义为参数相关的类型。 - 在C++14中,我们也可以去掉后置的返回类型,就让编译器去利用
auto 特性推断返回值类型:
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}
- 以上程序问题在于:元素类型为
T 的容器的 operator [] 返回的类型一般是 T& ,然而根据 Item 1,auto (实际上是按模板类型推导规则)推断出的返回类型会抹除引用性,导致返回值无法作为左值使用:
authAndAccess的返回值类型是int ,是一个右值
- 为此,C++14提出了
decltype(auto) 标识符。乍一看可能感觉这两者互相矛盾,但实际上它的语义是符合逻辑的:auto 指明类型需要被推导,decltype 表示推导过程应使用 decltype (而不是模板推导)的规则。 改写如下:
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}
decltype(auto) 的语法也适用于声明变量:
Widget w;
const Widget& cw = w;
auto myWidget1 = cw;
decltype(auto) myWidget2 = cw;
auto& myWidget3 = cw;
VS的贴心提示:
- 至此,还可能让你有点心神不宁的就是那个“仍需改进”,现在就让我们来处理它。
- 以上函数对容器的传参方式是左值的非常引用,因为我们允许用户对函数的返回值(也就是容器中的元素)进行修改。这带来一个问题:无法将右值作为参数传入。
- 诚然,这是一种edge case,因为传入的右值容器对象在当前调用函数语句结束时就被销毁,而维持的对容器元素的引用就会“悬空”(dangle)。
- 但还是有可能我们会想向函数中传入一个临时对象。例如,希望从临时容器中获得一个对象的复制:
std::deque<std::string> makeStringDeque();
auto s = authAndAccess(makeStringDeque(), 5);
- 为了支持这种功能,我们需要使函数同时能接受左值和右值参数。函数重载(Overloading)可行,但意味着我们要维护多一个函数。解决方法是使用同时可以绑定到左值和右值的万能引用来声明函数。
- 在函数定义中,还需对万能引用应用Item 25所述的
std::forward :
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i];
}
template<typename Container, typename Index>
auto authAndAccess(Container&& c, Index i) -> decltype(std::forward<Container>(c)[i])
{
authenticateUser();
return std::forward<Container>(c)[i];
}
decltype 的行为“几乎”总和你的预期一致,但还是有少数例外。这里举一个例子。decltype 对于左值表达式(非单一变量名),其返回值总是左值引用。这基本没有问题,因为大多数左值表达式本身都隐含了引用标识符。然而考虑以下情况: 将 x 改为 (x) 时,后者被认为是一个表达式,因此使用 decltype 推断出 int& 。仅仅加一对括号就可以改变推断的类型!如果这种情况还与返回值推导结合,就可能产生出更让程序员迷惑的结果:
decltype(auto) f1()
{
int x = 0;
return x;
}
decltype(auto) f2()
{
int x = 0;
return (x);
}
- 注意f2不仅是返回值类型与f1不同,它返回了一个指向函数局部变量的引用!undefined behavior在向你招手…
- 因此,使用
decltype(auto) 时要格外小心,因为一些看起来微不足道的改变就会带来完全错误的后果。
总结
decltype 几乎总会返回一个变量或表达式的未经修改的类别。- 对于非变量名的左值表达式,
decltype 总是返回引用。 - C++14支持
decltype(auto) ,它使用 decltype 的规则进行类型推导。
笔者注:开始感受到C++的力量了吗(笑)
|