c++11中的declval和decltype

本文介绍了C++中std::declval与decltype的功能与用法,包括如何绕过默认构造函数限制及推导表达式的类型。并通过多个示例展示了它们在模板元编程中的实际应用。

一、declval的介绍

std::declval定义在头文件中:

template<class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;

看定义它应该是返回一个右值引用(在T 是(可有 cv 限定的) void ,此情况下返回类型是 T)。在c++的文档中介绍说其可以不通过构造函数就可以使用T的成员函数,它只能用于不求值语境。这个模板函数没有具体的实现,无法调用。一般用于与decltype,sizeof等关键字配合来进行类型推导、占用内存空间(某一类型的对象的引用的占位符)计算等,一般在模板编程中应用较多。
需要注意的是,std::declval是在编译期进行处理完成的,也就是说,它在编译期过程中生成一个值对象,但是其并不会被编译为可执行期的二进制实体。或者可以这样理解,在开发过程中,有时候儿并不需要真正的拿到这个实例对象,而只是来对这个对象进行一个类型描述,如果真想用其来完成某种计算,一般外面会罩上decltype ,这也是在网上往往这两个模板函数放在一起分析的原因。这种情况是不是有些熟悉?在模板元编程里是不经常遇到这种情况,所以其在模板的元编程里应用也比较多。
下面看一个占位符的示例:

// 在下面的情况中,这里使用了std::declval,在调用运算符?:的时候就不需要调用 T1 和 T2 的(默认)构造函数
#include <utility>
template<typename T1, typename T2,
typename R = typename std::decay_t< decltype(true ? std::declval<T1>() : std::declval<T2>())> >
R GetMax (T1 a, T2 b)
{
    return b < a ? a : b;
}

弄明白了这个,就明白了std::declval的用处了。

二、decltype介绍

decltype可以在编译期内推导表达式所得值的类型,在上面的declval中可以看到一起使用的效果。其实看代码也可以明白,就是推导出类型结果。拿到这个类型结果,就可以搞事情了。可是这里面有一个问题,为什么它是一个关键字?这个类型推导多么简单的一个事儿。其实不然,如果后面有一大堆的表达式,复杂的不得了,你就会发现,用这个关键字可就真的好。那什么情况下会有一大串的表达式呢?仍然是元编程中多。谁也不想把一大串的模板声明不断的抄来抄去,这老麻烦了。也就是说,decltype,auto,using在某些情况下起到的作用有些类似。在前面也可以看到这些用法的使用,这里就不再做赘述。它的语法定义如下:

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

decltype为什么配合std::declval一起使用,有一个重要原因就是前者需要推导类型时对象要求需要有默认构造函数,而后者不需要。这就在模板元编程中起到了重要的作用。另外,纯虚类也是一个问题,declval 可以绕开纯虚基类不能实例化的问题。decltype和逗号表达式一起工作时,需要考虑逗号表达式是从左到右依次计算,最后一个做为返回值,这个在以前的变参模板中也用到过。看一下例子:

template<typename T>
auto len (T const&& t) -> decltype((void)(t.size()) , T::size_type)
{
    return t.size();
}

这个size函数可以说做一个Assert,如果T中有这个函数,则替换成功,否则直接报错。这也算是一个小技巧。

三、例程

这两个功能可以在一起使用,然后创造一些小惊喜。
看一看std::declval的例程:

#include <utility>
#include <iostream>
 
struct Default { int foo() const { return 1; } };
 
struct NonDefault
{
    NonDefault() = delete;
    int foo() const { return 1; }
};
 
int main()
{
    decltype(Default().foo()) n1 = 1;                   // n1 的类型是 int
//  decltype(NonDefault().foo()) n2 = n1;               // 错误:无默认构造函数
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // n2 的类型是 int
    std::cout << "n1 = " << n1 << '\n'
              << "n2 = " << n2 << '\n';
}

是不是非常简单明了,其实学习这些东西,就得从最基础最简单的地方,把基本的知识掌握了,才能使用上面的各种变化和技巧。

再看一下decltype的例程:

#include <iostream>
#include <type_traits>
 
struct A { double x; };
const A* a;
 
decltype(a->x) y;       // y 的类型是 double(其声明类型)
decltype((a->x)) z = y; // z 的类型是 const double&(左值表达式)
 
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) // 返回类型依赖于模板形参
{                                     // C++14 开始可以推导返回类型
    return t+u;
}
 
int main() 
{
    int i = 33;
    decltype(i) j = i * 2;
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
 
    std::cout << "i 和 j 的类型相同吗?"
              << (std::is_same_v<decltype(i), decltype(j)> ? "相同" : "不同") << '\n';
 
    auto f = [](int a, int b) -> int
    {
        return a * b;
    };
 
    decltype(f) g = f; // lambda 的类型是独有且无名的
    i = f(2, 2);
    j = g(3, 3);
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
}

在c++17以后,还可以std::delctype(auto)这种形式来推导相关的类型,例程可以看一下前面的拖尾类型的文章,上面有c++14和c++17两种的不同方式,就用到这种形式。
看了上面的两个应用,再看一个网上的纯虚类的应用:

#include <iostream>

namespace {
  struct base_t { virtual ~base_t(){} };

  template<class T>
    struct Base : public base_t {
      virtual T t() = 0;
    };

  template<class T>
    struct A : public Base<T> {
      ~A(){}
      virtual T t() override { std::cout << "A" << '\n'; return T{}; }
    };
}

int main() {
  decltype(std::declval<A<int>>().t()) a{}; // = int a;
  decltype(std::declval<Base<int>>().t()) b{}; // = int b;
  std::cout << a << ',' << b << '\n';
}

四、总结

其实很多东西,基础的太简单,仿佛看一眼就会了。但是过了好久,又突然发现,这种简单的东西自己从来没用过。也不问为什么,反正是觉得用不到。忽然有一天,看到人家大牛的代码里这种简单的知识组合起来满飞,大脑于是一片迷茫,根本不知道怎么回事儿。回过头来看吧,又觉得基础的东西太多,不看吧,确实又不明白代码。问人吧,又没人可问。于是,大多数人可能就不了了之了。
学习在这里突然断了片儿,上,够不着;下,不甘心。怎么破除这种情况呢?还是得反过来,把基础重新牢牢打好,把大牛的复杂代码分解成一块一块的不断分析,从细节入手,除了一些特别孤僻的技巧,一般来说,都会慢慢搞定。从生到熟,从熟到初步应用,到自主应用,甚至自由组合放飞技术。这都是大有可能的。努力吧!

<think>我们正在讨论C++中的decltype关键字。根据引用[1][2][3],decltypeC++11引入的关键字,用于在编译时查询表达式的类型,而不实际计算表达式。它对于模板元编程、自动类型推导的复杂场景以及需要精确控制类型信息的场合非常有用。 我将按照以下结构来组织回答: 1. decltype的基本语法用法 2. decltype的常见使用场景 3. decltype与其他类型推导(如auto)的区别 4. decltype的注意事项陷阱(特别是双层括号问题) 5. 总结 同时,根据要求,我会在回答中自然地添加引用标识,并在最后生成相关问题。 注意:在回答中,行内数学表达式使用$...$,独立公式使用$$...$$(但本问题中不涉及数学公式,所以主要关注代码文字描述)。 开始回答:</think>### C++中`decltype`的使用与注意事项 `decltype`是C++11引入的关键字,用于在编译时推导表达式的类型而不实际计算表达式值[^2]。它在模板元编程类型安全场景中尤为重要。 --- #### 一、基本用法 1. **推导变量类型** ```cpp int x = 10; decltype(x) y = x; // y的类型为int ``` - 直接推导表达式类型(保留`const`引用限定)[^3]: ```cpp const int& z = x; decltype(z) w = x; // w的类型为const int& ``` 2. **作为函数返回类型** ```cpp template<typename T, typename U> auto add(T a, U b) -> decltype(a + b) { return a + b; } // 返回类型由a+b的表达式类型决定 ``` 3. **结合`auto`使用(C++14起)** ```cpp decltype(auto) func() { return x; // 返回类型精确匹配x的原始类型(含引用/const) } ``` --- #### 二、关键应用场景 1. **泛型编程** 在模板中处理未知类型: ```cpp template<typename Container> void process(Container& c) { decltype(c[0]) elem = c[0]; // 推导容器元素类型(含引用) } ``` 2. **类型安全代码** 避免隐式类型转换: ```cpp double d = 3.14; decltype(d) i = 5; // i为double而非int ``` 3. **元编程与SFINAE** 结合`std::declval`推导表达式可行性: ```cpp template<typename T> auto has_foo(T t) -> decltype(t.foo(), bool()) { return true; } ``` --- #### 三、注意事项与陷阱 1. **双层括号陷阱** `decltype((x))`始终返回**引用类型**(左值表达式)[^3]: ```cpp int x = 0; decltype(x) a = x; // a: int decltype((x)) b = x; // b: int& ``` 2. **与`auto`的区别** | 特性 | `auto` | `decltype` | |--------------|----------------------------|---------------------------| | 引用推导 | 默认去除引用(`auto&&`除外) | 保留引用 | | 顶层`const` | 忽略 | 保留 | | 数组类型 | 退化为指针 | 保留数组类型(如`int[5]`)| 3. **避免在运行时计算** `decltype`仅静态分析表达式类型,不会执行代码: ```cpp int* p = nullptr; decltype(*p) c; // 合法:c为int&(即使p为nullptr) ``` --- #### 四、最佳实践 1. **优先使用场景** - 需要精确控制返回类型时(如通用库开发) - 推导依赖于模板参数的表达式类型 - 需要保留引用或`const`限定符时 2. **替代方案选择** - 简单变量初始化:用`auto` - 函数返回类型简化:C++14+用`auto`(推导规则不同) 3. **结合现代特性** C++17结构化绑定: ```cpp std::map<int, std::string> m; for (const auto& [key, val] : m) { decltype(key) k = key; // k为const int } ``` --- ### 总结 `decltype`提供了编译期类型查询能力,特别适合需要精确类型控制的场景(如模板元编程)。使用时需警惕双层括号陷阱,并与`auto`互补使用以平衡代码简洁性类型安全性[^1][^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值