引言
最近在重新复读《C++ template》相关知识,因此预计总结相关知识汇成template系列。
目前碰到decltype与std::declval相关知识,为了搞懂decltype和std::declval的搭配使用原理,我参考了如下链接,
Why does std::declval add a reference?
What are unevaluated operands in C++?
最终总结了本篇文章。
本文章主要包括如下部分:
unevaluated operands
std::declval实现
decltype specifier以及示例代码
unevaluated operands
关于什么是unevaluated operands,在What are unevaluated operands in C++?有如下定义
In some contexts, unevaluated operands appear [...].
An unevaluated operand is not evaluated.An unevaluated operand is considered a full-expression.
unevaluated operand是C++语言中某些操作符(some operators)的操作数。这些操作符是表达式,但是他们从来不会产生运行时开销,不会被评估(evaluated)。这些操作符仅仅在编译时查询他们操作数的编译期属性。
在C++20之前,这些操作符有四个:typeid
, sizeof
, decltype
, and noexcept。
std::declval实现
首先需要回答一个问题:std::declval是什么?
该定义来自我的理解,不具有官方意义,仅供参考。
std::declval从实现角度来说,就是一个函数模版的声明, 其属于unevaluated operand。
那么为什么需要std::declval呢?
思考这样一个场景: 一个类A,其构造函数被delete,然后想要通过decltype获取其类型。那么该如何获得呢?
也许你想通过如下代码获得
using AType = decltype(A());
由于A的构造函数声明为delete,因此上述编译器会报错,
error: use of deleted function ‘main()::A::A()’
然而若使用如下实现,则构建不会报错
using AType = decltype(A{});
上述完整代码可参考如下
int main() {
class A {
public:
A() = delete;
};
using AType = decltype(A{});
return 0;
}
注:该代码在C++17标准下编译构建。
当然除了利用{}初始化,C++标准库提供了另一个工具 也即是std::declval, 来获得A的类型。
其使用如下:
using AType = decltype(std::declval<A>());
上述代码编译也能成功。
关于为什么{}, std::declval可以编译成功,而A()形式构建失败,可参考下一节说明。
在此,给出标准库中关于std::declval的定义
template< class T >
typename std::add_rvalue_reference<T>::type declval() noexcept;
decltype specifier以及示例代码
decltype的作用可参考decltype specifier - cppreference.com,简言之,
decltype 检查实体的声明类型或表达式的类型和值类别。
关于值类别的细节可参考Value categories - cppreference.com或者《C++ Templates》相关章节。
在decltype specifier - cppreference.com中,关于decltype有如下一句话
If expression is a prvalue other than a (possibly parenthesized) immediate invocation (since C++20), a temporary object is not materialized from that prvalue: such prvalue has no result object.
简言之,decltype的操作数的值类型如果是一个prvalue,那么decltype表达式是合法的。
因此,便可以解释上述中 decltype(A{}), decltype(std::declval<A>())的正确性了。
因为A{}为一个prvalue,std::declval<A>()为一个A&&,因此两者都正确。
为了更好的验证结果,也可在C++ Insights中,观察生成后的代码。譬如下述代码
#include <utility>
#include <type_traits>
#include <iostream>
template <typename T1, typename T2,
typename RT = std::decay_t<decltype(true ? std::declval<T1>() : std::declval<T2>())>>
RT max(T1 a, T2 b) {
return b < a ? a : b;
}
int main() {
class A {
public:
A() = delete;
};
using AType = decltype(A{});
using AType1 = decltype(std::declval<A>());
::max(1.0, 2);
return 0;
}
在其中生成的代码片段如下
/* First instantiated from: insights.cpp:18 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
double max<double, int, double>(double a, int b)
{
return static_cast<double>(b) < a ? a : static_cast<double>(b);
}
#endif
class A
{
public:
// inline A() = delete;
};
using AType = A;
using AType1 = A &&;
总结
在此总结了std::declval的功能,以及decltype与std::declval的联合使用。通过该篇文章,应该可以加深对两者的理解。