std::declval 的作用

std::declval 的作用

std::declval 是 C++ 标准库中的一个工具,用于在不创建对象的情况下获得一个类型的引用。其主要作用是在编译时期在表达式中使用一个类型的实例而不实际构造对象。这在模板元编程和 SFINAE 中尤其有用,因为它允许我们在编译时对类型的潜在成员进行推断和检查。

std::declval<utility> 头文件中定义,并且仅在 unevaluated context(未求值上下文,例如 sizeof 和 decltype 中)是合法的,这意味着它不能用于在运行时创建对象。

std::declval 的使用场景

1. 类型推导

最常见的场景是在使用 decltype 来推导表达式的类型时,尤其是当你需要推导一个类的成员函数的返回类型,但又不想或不能创建这个类的实例。

例如,下面的代码展示了如何使用 std::declval 来推导成员函数的返回类型:

1
2
3
4
5
6
7
8
#include <utility>

struct MyClass {
int myFunction() const { return 42; }
};

// 不需要创建 MyClass 的实例
decltype(std::declval<MyClass>().myFunction()) myVar;

在这里,decltype 用来确定 myFunction 的返回类型,而 std::declval<MyClass>() “假装” 有一个 MyClass 的对象来允许我们调用成员函数。

2. SFINAE 和类型萃取

在 SFINAE(替换失败并非错误)或写类型萃取(trait)时,std::declval 能够确保在没有默认构造函数的情况下依然能进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <type_traits>
#include <utility>

struct MyClass {
using value_type = int;
value_type myFunction() const { return 42; }
};

// 一个用于萃取成员函数返回类型的类型萃取
template<typename T>
struct ReturnType {
using type = decltype(std::declval<T>().myFunction());
};

static_assert(std::is_same<ReturnType<MyClass>::type, int>::value, "Type must be int");

在这个例子中,即使 MyClass 没有默认构造函数,我们也能用 std::declval 来进行编译时检查。

3. 重载决策

在某些复杂的重载决策场景中,你可能希望根据类型是否支持某个操作来选择不同的函数重载。std::declval 可以在不创建对象的情况下 “假设” 该对象的存在,以便进行这样的检查。

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
26
27
#include <iostream>
#include <type_traits>
#include <utility>

struct A {};

struct B {
void print() const { std::cout << "B::print" << std::endl; }
};

template<typename T>
typename std::enable_if<std::is_member_function_pointer<decltype(&T::print)>::value>::type
testPrint(T&& t) {
t.print();
}

template<typename T>
void testPrint(T&&) {
std::cout << "No print function" << std::endl;
}

int main() {
A a;
B b;
testPrint(a); // 输出 "No print function"
testPrint(b); // 输出 "B::print"
}

这里 std::declval 在编译时帮助确定 T 类型是否有一个名为 print 的成员函数。

小结

std::declval 是模板编程中一个极为有用的工具,它能够让程序员在不实例化对象的情况下引用任意类型,特别是在仅仅需要类型信息而不需要对象的场合。它通常与 decltype 结合使用,以便在编译时对表达式类型进行操作和推导。由于 std::declval 仅在未求值上下文中有效,因此它

主要用于模板元编程和编译时类型推导,而不是在实际运行时代码中创建对象。