template系列--decltype与std::declval

引言

最近在重新复读《C++ template》相关知识,因此预计总结相关知识汇成template系列。

目前碰到decltype与std::declval相关知识,为了搞懂decltype和std::declval的搭配使用原理,我参考了如下链接,

decltype specifier

Why does std::declval add a reference?

What are unevaluated operands in C++?

C++ Templates(第2版 英文版)

最终总结了本篇文章。

本文章主要包括如下部分:

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之前,这些操作符有四个typeidsizeofdecltype, 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的联合使用。通过该篇文章,应该可以加深对两者的理解。

In file included from /home/yhdr/2-test-2023-06_v3/sent.h:24:0, from /home/yhdr/2-test-2023-06_v3/sent.cpp:1: /usr/include/c++/7/thread: In instantiation of ‘struct std::thread::_Invoker<std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> > >’: /usr/include/c++/7/thread:127:22: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(double*, double&, double&, double&, double&, double&); _Args = {double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>}]’ /home/yhdr/2-test-2023-06_v3/sent.cpp:18:153: required from here /usr/include/c++/7/thread:240:2: error: no matching function for call to ‘std::thread::_Invoker<std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> > >::_M_invoke(std::thread::_Invoker<std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> > >::_Indices)’ operator()() ^~~~~~~~ /usr/include/c++/7/thread:231:4: note: candidate: template<long unsigned int ..._Ind> decltype (std::__invoke((_S_declval<_Ind>)()...)) std::thread::_Invoker<_Tuple>::_M_invoke(std::_Index_tuple<_Ind ...>) [with long unsigned int ..._Ind = {_Ind ...}; _Tuple = std::tuple<void (*)(double*, double&, double&, double&, double&, double&), double**, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double>, std::reference_wrapper<double> >] _M_invoke(_Index_tuple<_Ind...>)
06-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

qls315

感觉好可打赏几毛钱增强更新动力

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

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

打赏作者

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

抵扣说明:

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

余额充值