Effective Modern C++[实践]->理解decltype

何谓decltype

decltype 是“declare type”的缩写,译为“声明类型”。
decltype说明符是c++ 11引入的另一个新特性。decltype检查实体的声明类型,或表达式的类型和值类别。
语法如下:

decltype ( 实体 ) 	  (1) 	  (C++11)
decltype ( 表达式 ) 	  (2) 	  (C++11)

推导规则说明:

规则1

  1. 如果实参是没有括号的标识表达式或没有括号的类成员访问表达式,那么 decltype 产生以该表达式命名的实体的类型。如果没有这种实体或该实参指名了一组重载函数,那么编译错误。
  • 示例1
#include <vector>
int main() {
	int x = 42;
    std::vector<decltype(x)> v(100, x); // v 是一个 vector<int>
}

生成的代码如下

#include <vector>
int main(){
  int x = 42;
  std::vector<decltype(x)> v = std::vector<int, std::allocator<int> >(100, x, std::allocator<int>());
  return 0;
}
  • 示例2
struct S {
    int x = 42;
};
const S s;
decltype(s.x) y; // Y的类型是int,尽管s.x是const

生成的代码如下

 struct S{
  int x;
  // inline constexpr S() noexcept = default;
};
int main(){
  const S s = S();
  int y;
  return 0;
}
  • 示例3
int func(){
    return 0;
}
int func(int a){
    return 0;
}
int main(){
    int i = 4;
    // 不正确的使用。Func为重载函数命名
    decltype(func) var1;
    // 正确的使用。重载操作没有歧义
    decltype(func(i)) var2;
    return 0;
}

规则2

  1. 如果实参是其他类型为 T 的任何表达式,且
    1. 如果 表达式 的值类别是亡值,将会 decltype 产生 T&&
    2. 如果 表达式 的值类别是左值,将会 decltype 产生 T&
    3. 如果 表达式 的值类别是纯右值,将会 decltype 产生 T。该类型不需要是完整类型或拥有可用的析构函数,而且类型可以是抽象的。此规则不适用于其子表达式:decltype(f(g())) 中,g() 必须有完整类型,但 f() 不必。

注意如果对象的名字带有括号,那么它会被当做通常的左值表达式,从而 decltype(x)decltype((x)) 通常是不同的类型。在难以或不可能以标准写法进行声明的类型时,decltype 很有用,例如 lambda 相关类型或依赖于模板形参的类型。
示例1

int f() { return 42; }
int& g() { static int x = 42; return x; }

int main() {
	int x = 42;
	decltype(f()) a = f(); // a的类型是int
	decltype(g()) b = g(); // b的类型是int&
	decltype((x)) c = x;   // c的类型是int&,因为x是左值
}

生成的代码如下:

int f(){ return 42;}
int & g(){
  static int x = 42;
  return x;
}
int main(){
  int x = 42;
  int a = f();
  int & b = g();
  int & c = x;
  return 0;
}

综合示例:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

struct A { double x; };

int&& f1(void);  //返回值为 int&&

int main() {
    int var = 1;
    
    const A *a = new A();
    decltype(f1()) a1 = 0 ;
	decltype(var) a2 ;
	decltype(a->x) a3;
    decltype((a->x)) a4 =11;
}

编译生成代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

struct A
{
  double x;
};
int && f1();
  //返回值为 int&&

int main()
{
  int var = 1;
  const A * a = new A();
  
  int && a1 = 0;//decltype(f1()) a1 = 0 ;
  int a2;//decltype(var) a2 ;
  double a3;//decltype(a->x) a3;
  const double & a4 = 11;//decltype((a->x)) a4 =11;
  return 0;
}
decltype类型说明
decltype(f1()) a1const int&& 一个指向const int对象的右值引用。
decltype(var);int 变量var的类型。
decltype(a->x); double成员访问的类型。
decltype((a->x)) a4const double&内部圆括号导致语句被作为表达式而不是成员访问进行计算。由于a被声明为const指针,该类型是const double类型的引用。

应用

应用1

c++11中,比较典型的应用示例是decltypetype\using的合用。

using size_t = decltype(sizeof(0));
using ptrdiff_t = decltype((int*)0-(int*)0);
using nullptr_t = decltype(nullptr);

应用2

  1. c++11
template<typename T, typename U>
auto add(T&& t, U&& u) -> decltype(t+u) { return t + u; }

int main() {
	int x = 42;
  	double y = 77.22;
  	add(x,y);
}

生成代码如下:

template<typename T, typename U>
auto add(T&& t, U&& u) -> decltype(t+u) { return t + u; }

/* First instantiated from: insights.cpp:8 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
double add<int &, double &>(int & t, double & u)
{
  return static_cast<double>(t) + u;
}
#endif
int main()
{
  int x = 42;
  double y = 77.219999999999999;
  add(x, y);
  return 0;
}
  1. c++14
template<typename T, typename U>
decltype(auto) add1(T&& t, U&& u){ return t + u; }

int main() {
	int x = 42;
  	double y = 77.22;
  	add1(x,y);
}

生成的代码如下:

template<typename T, typename U>
decltype(auto) add1(T&& t, U&& u){ return t + u; }

#ifdef INSIGHTS_USE_TEMPLATE
template<>
double add1<int &, double &>(int & t, double & u){
  return static_cast<double>(t) + u;
}
#endif
int main(){
  int x = 42;
  double y = 77.219999999999999;
  add1(x, y);
  return 0;
}

书中示例研习

缺陷代码1

template<typename T, typename U>
auto authAndAcess(T &t,U u){
	return t[u];
}

此代码调用authAndAcess(d,5)后推导代码如下:

template<>
int authAndAcess<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
  return t.operator[](u);
}

原因为:auto关键字进行类别推导时会忽略引用特性,因此虽然d[5]返回的时int &,但是最终却是int的结果。因此无法执行authAndAcess(d,5)=10

缺陷代码2

//推导为int &,但是无法接收右值容器
template<typename T, typename U>
decltype(auto) authAndAcess1(T &t,U u){
	return t[u];
}

此代码调用authAndAcess(d,5)后推导代码如下:

template<>
int & authAndAcess1<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
  return t.operator[](u);
}

可以实现赋值了,但是形参第一个无法接收右值

c++14的最终版本如下

#include <iostream>
#include <deque>
using namespace std;
//推导为int ,无法对返回值赋值
template<typename T, typename U>
auto authAndAcess(T &t,U u){
	return t[u];
}
//推导为int &,但是无法接收右值容器
template<typename T, typename U>
decltype(auto) authAndAcess1(T &t,U u){
	return t[u];
}
//c++ 14的最终版本
template<typename T, typename U>
decltype(auto) 
authAndAcess2(T &&t,U u){
	return std::forward<T>(t)[u];
}

std::deque<std::string> makeStringDeque(){
	return {"1","2","3","4","5"};
}
int main() {
	std::deque<int> d={0,1,2,3,4,5,6,7};
  
  	authAndAcess(d,5);
  	authAndAcess1(d,5)=10;
    //note: candidate function [with T = std::deque<std::basic_string<char>>, U = int] not viable: expects an lvalue for 1st argument
  	//authAndAcess1(makeStringDeque(),2);//第一个参数是左值,不能传入右值
  	auto s = authAndAcess2(makeStringDeque(),5);
  	authAndAcess2(d,5)= 12;
  	//std::deque<std::string> makeStringDeque();
}

编译后代码生成的代码如下:

#include <iostream>
#include <deque>
using namespace std;
//推导为int ,无法对返回值赋值
template<typename T, typename U>
auto authAndAcess(T &t,U u){
	return t[u];
}

#ifdef INSIGHTS_USE_TEMPLATE
template<>
int authAndAcess<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
  return t.operator[](u);
}
#endif

//推导为int &,但是无法接收右值容器
template<typename T, typename U>
decltype(auto) authAndAcess1(T &t,U u){
	return t[u];
}

#ifdef INSIGHTS_USE_TEMPLATE
template<>
int & authAndAcess1<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
{
  return t.operator[](u);
}
#endif

//c++ 14的最终版本
template<typename T, typename U>
decltype(auto) authAndAcess2(T &&t,U u){
	return std::forward<T>(t)[u];
}

#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char> & authAndAcess2<std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > >, int>(std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > && t, int u)
{
  return std::forward<std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > >(t).operator[](static_cast<unsigned long>(u));
}
#endif


#ifdef INSIGHTS_USE_TEMPLATE
template<>
int & authAndAcess2<std::deque<int, std::allocator<int> > &, int>(std::deque<int, std::allocator<int> > & t, int u)
{
  return std::forward<std::deque<int, std::allocator<int> > &>(t).operator[](static_cast<unsigned long>(u));
}
#endif


std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > makeStringDeque()
{
  return std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > >{std::initializer_list<std::basic_string<char> >{std::basic_string<char>(std::basic_string<char>("1", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("2", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("3", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("4", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("5", std::allocator<char>()))}, std::allocator<std::basic_string<char> >()};
}

int main()
{
  std::deque<int> d = std::deque<int, std::allocator<int> >{std::initializer_list<int>{0, 1, 2, 3, 4, 5, 6, 7}, std::allocator<int>()};
  authAndAcess(d, 5);
  authAndAcess1(d, 5) = 10;
  std::basic_string<char> s = std::basic_string<char>(authAndAcess2(makeStringDeque(), 5));
  authAndAcess2(d, 5) = 12;
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-西门吹雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值