C++之std::decay

发布时间:2023年12月29日

1.简介

std::decay是C++11之后引进的模板编程工具,它的主要作用是将给定的类型T转换为它的“衰变”类型。这个“衰变”类型是指去除类型T的所有引用、常量和易变性限定符,以及将所有数组和函数转换为对应的指针类型后得到的类型;在头文件?<type_traits>?中定义:

template< class T >
struct decay;

2.辅助类

2.1.std::remove_reference和std::remove_reference_t

先看实现:

template <class _Ty>
struct remove_reference {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty;
};

template <class _Ty>
struct remove_reference<_Ty&> {       //左值引用
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty&;
};

template <class _Ty>
struct remove_reference<_Ty&&> {       //右值引用
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty&&;
};

template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;

从上面的代码实现来看,它的作用就是擦除类型引用,无论是左值引用还是右值引用,例如,如果_Ty是int&int&&,那么std::remove_reference_t<_Ty>就是int。示例如下:

#include <iostream> // std::cout
#include <type_traits> // std::is_same
 
template<class T1, class T2>
void print_is_same() {
  std::cout << std::is_same<T1, T2>() << '\n';
}
 
int main() {
  std::cout << std::boolalpha;
 
  print_is_same<int, int>();   //输出: true
  print_is_same<int, int &>();  //输出: false
  print_is_same<int, int &&>();  //输出: false
 
  print_is_same<int, std::remove_reference<int>::type>(); //输出: true
  print_is_same<int, std::remove_reference<int &>::type>(); //输出: true
  print_is_same<int, std::remove_reference<int &&>::type>(); //输出: true
}

2.2.std::remove_cv

用于擦除类型的const和volatile限定符,返回的是一个去除了const和volatile限定符的类型,例如,如果T是const intvolatile int,那么std::remove_cv_t<T>就是int它的实现如下:

template <class _Ty>
struct remove_cv { // remove top-level const and volatile qualifiers
    using type = _Ty;

    template <template <class> class _Fn>
    using _Apply = _Fn<_Ty>; // apply cv-qualifiers from the class template argument to _Fn<_Ty>
};

template <class _Ty>
struct remove_cv<const _Ty> {    //擦除const
    using type = _Ty;

    template <template <class> class _Fn>
    using _Apply = const _Fn<_Ty>;
};

template <class _Ty>
struct remove_cv<volatile _Ty> {  //擦除volatile 
    using type = _Ty;

    template <template <class> class _Fn>
    using _Apply = volatile _Fn<_Ty>;
};

template <class _Ty>
struct remove_cv<const volatile _Ty> { //擦除const volatile
    using type = _Ty;

    template <template <class> class _Fn>
    using _Apply = const volatile _Fn<_Ty>;
};

template <class _Ty>
using remove_cv_t = typename remove_cv<_Ty>::type;

示例如下:

#include <iostream>
#include <type_traits>
 
int main() {
    typedef std::remove_cv<const int>::type type1;
    typedef std::remove_cv<volatile int>::type type2;
    typedef std::remove_cv<const volatile int>::type type3;
    typedef std::remove_cv<const volatile int*>::type type4;
    typedef std::remove_cv<int * const volatile>::type type5;
 
    std::cout << "test1 " << (std::is_same<int, type1>::value
        ? "passed" : "failed") << '\n';
    std::cout << "test2 " << (std::is_same<int, type2>::value
        ? "passed" : "failed") << '\n';
    std::cout << "test3 " << (std::is_same<int, type3>::value
        ? "passed" : "failed") << '\n';
    std::cout << "test4 " << (std::is_same<const volatile int*, type4>::value
        ? "passed" : "failed") << '\n';
    std::cout << "test5 " << (std::is_same<int*, type5>::value
        ? "passed" : "failed") << '\n';
}

输出:

test1 passed
test2 passed
test3 passed
test4 passed
test5 passed

2.3.std::is_array

template< class T >
struct is_array;

主要是判断?T 是否为数组类型,如果?T?为数组类型,那么成员常量?value?等于?true。对于其它类型,value?等于?false,? 它的实现如下:

template <class>
_INLINE_VAR constexpr bool is_array_v = false; // determine whether type argument is an array

template <class _Ty, size_t _Nx>
_INLINE_VAR constexpr bool is_array_v<_Ty[_Nx]> = true;

template <class _Ty>
_INLINE_VAR constexpr bool is_array_v<_Ty[]> = true;

template <class _Ty>
struct is_array : bool_constant<is_array_v<_Ty>> {};

从源码很清晰的看到当输入为数组类型的时候,特例化返回true。示例如下:

#include <array>
#include <iostream>
#include <type_traits>
 
class A {};
 
int main() 
{
    std::cout << std::boolalpha;
    std::cout << std::is_array<A>::value << '\n';  //输出: false
    std::cout << std::is_array<A[]>::value << '\n'; //输出: true
    std::cout << std::is_array<A[3]>::value << '\n'; //输出: true
    std::cout << std::is_array<float>::value << '\n';  //输出: false
    std::cout << std::is_array<int>::value << '\n';   //输出: false
    std::cout << std::is_array<int[]>::value << '\n';  //输出: true
    std::cout << std::is_array<int[3]>::value << '\n';  //输出: true 
    std::cout << std::is_array<std::array<int, 3>>::value << '\n'; //输出: false
}

2.4.std::is_function

std::is_function(C++11)用于检查类型是否为函数,注意此处函数类型不包括std::function, lambda, 有重载operator()的类,它的实现如下:

template <class _Ty>
_INLINE_VAR constexpr bool is_function_v = // only function types and reference types can't be const qualified
    !is_const_v<const _Ty> && !is_reference_v<_Ty>;

template <class _Ty>
struct is_function : bool_constant<is_function_v<_Ty>> {};

示例如下:

#include <iostream>
#include <type_traits>
 
struct A {
    int fun() const&;
};
 
template<typename>
struct PM_traits {};
 
template<class T, class U>
struct PM_traits<U T::*> {
    using member_type = U;
};
 
int f();
 
int main() 
{
    std::cout << std::boolalpha;
    std::cout << std::is_function<A>::value << '\n';   //输出 : false
    std::cout << std::is_function<int(int)>::value << '\n';  //输出 : true
    std::cout << std::is_function<decltype(f)>::value << '\n'; //输出 : true
    std::cout << std::is_function<int>::value << '\n';  //输出 : false
 
    using T = PM_traits<decltype(&A::fun)>::member_type; // T 为 int() const&
    std::cout << std::is_function<T>::value << '\n';     //输出 : true
}

2.5.std::remove_extent

它的作用是给数组移除一个维度,注意若 T 是多维数组,则只移除第一维。它的实现如下:

template <class _Ty>
struct remove_extent { // remove array extent
    using type = _Ty;
};

template <class _Ty, size_t _Ix>
struct remove_extent<_Ty[_Ix]> {
    using type = _Ty;
};

template <class _Ty>
struct remove_extent<_Ty[]> {
    using type = _Ty;
};

template <class _Ty>
using remove_extent_t = typename remove_extent<_Ty>::type;

多维数组,如果要全部移除维度,则要用std::remove_all_extents, std::remove_all_extents实现如下:

template <class _Ty>
struct remove_all_extents { // remove all array extents
    using type = _Ty;
};

template <class _Ty, size_t _Ix>
struct remove_all_extents<_Ty[_Ix]> {
    using type = typename remove_all_extents<_Ty>::type;
};

template <class _Ty>
struct remove_all_extents<_Ty[]> {
    using type = typename remove_all_extents<_Ty>::type;
};

template <class _Ty>
using remove_all_extents_t = typename remove_all_extents<_Ty>::type;

这里巧妙的借助元编程中模板类的偏特化和递归继承属性: 定义的remove_all_extents偏特化版本继承自身,通过不断地递归继承,直到remove_all_extents<T>类型与T类型完全相同时调用普通版本,完成递归调用;所有的递归调用过程均在编译期完成,从这个例子中可以看出模板元编程的真正魅力所在。

示例如下:

#include <iostream>
#include <iterator>
#include <algorithm>
#include <type_traits>
 
template<class A>
typename std::enable_if< std::rank<A>::value == 1 >::type
print_1d(const A& a)
{
    copy(a, a+std::extent<A>::value,
         std::ostream_iterator<typename std::remove_extent<A>::type>(std::cout, " "));
    std::cout << '\n';
}
 
int main()
{
    int a[][3] = {{1,2,3},{4,5,6}};
//  print_1d(a); // 编译时错误
    print_1d(a[1]);  //输出: 4  5  6
}

2.6.std::add_pointer

std::add_pointer模板可以把一个类型转换为指针类型,它的实现:

template <class _Ty, class = void>
struct _Add_pointer { // add pointer (pointer type cannot be formed)
    using type = _Ty;
};

template <class _Ty>
struct _Add_pointer<_Ty, void_t<remove_reference_t<_Ty>*>> { // (pointer type can be formed)
    using type = remove_reference_t<_Ty>*;
};

template <class _Ty>
struct add_pointer {
    using type = typename _Add_pointer<_Ty>::type;
};

template <class _Ty>
using add_pointer_t = typename _Add_pointer<_Ty>::type;

示例如下:

#include <iostream>
#include <type_traits>
 
using namespace std;
 
int main() {
    typedef add_pointer<int>::type ptr; // ptr 是 int* 类型
    cout << is_same<int*, ptr>::value << endl;  // 输出 true
    return 0;
}

????????在上面例子中,我们使用add_pointer模板将类型int转换为指针类型int*,并声明了别名ptr。可以看到,使用add_pointer模板可以快速实现类型转换。

2.7.std::conditional

std::conditional模板可以根据条件进行类型选择, 实现如下:

template <bool _Test, class _Ty1, class _Ty2>
struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwise
    using type = _Ty1;
};

template <class _Ty1, class _Ty2>
struct conditional<false, _Ty1, _Ty2> {
    using type = _Ty2;
};

template <bool _Test, class _Ty1, class _Ty2>
using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;

示例如下:

#include <iostream>
#include <type_traits>
#include <typeinfo>
 
int main() 
{
    typedef std::conditional<true, int, double>::type a;
    typedef std::conditional<false, int, double>::type b;
    typedef std::conditional<sizeof(int) >= sizeof(double), int, double>::type c;
 
    std::cout << typeid(a).name() << '\n';
    std::cout << typeid(b).name() << '\n';
    std::cout << typeid(c).name() << '\n';
}

3.可能实现

介绍完上面的基础知识后,根据std::decay的功能,猜测大致可能的实现如下:

template<class T>
struct decay
{
private:
    typedef typename std::remove_reference<T>::type U;
public:
    typedef typename std::conditional< 
        std::is_array<U>::value,
        typename std::remove_extent<U>::type*,
        typename std::conditional< 
            std::is_function<U>::value,
            typename std::add_pointer<U>::type,
            typename std::remove_cv<U>::type
        >::type
    >::type type;
};

分析步骤:

1)? 擦除引用类型。例如 int&和int&& 返回 int

2)如果是数组,返回的就是数组存放的数据类型。例如:int[10] 返回 int

3)如果是函数,返回的就是函数指针。例如:int (int) 返回 int (*)(int)

4)? 除2和3之外的其它类型,则擦除const、volatile 或 const?volatile。

4.源码分析

根据第3部分讲解的实现流程,在VS2019上是怎么实现上面的功能呢?不隐藏,直接上源码:

template <bool>
struct _Select { // Select between aliases that extract either their first or second parameter
    template <class _Ty1, class>
    using _Apply = _Ty1;
};

template <>
struct _Select<false> {
    template <class, class _Ty2>
    using _Apply = _Ty2;
};


template <class _Ty>
struct decay { // determines decayed version of _Ty
    using _Ty1 = remove_reference_t<_Ty>;
    using _Ty2 = typename _Select<is_function_v<_Ty1>>::template _Apply<add_pointer<_Ty1>, remove_cv<_Ty1>>;
    using type = typename _Select<is_array_v<_Ty1>>::template _Apply<add_pointer<remove_extent_t<_Ty1>>, _Ty2>::type;
};

template <class _Ty>
using decay_t = typename decay<_Ty>::type;

从上面的代码可以看出,把std::conditional替换成了_Select,而_Select的功能和std::conditional是一模一样的,代码的逻辑和第3部分分析的也是一模一样的,在这里就不再过多赘述了。

5.使用

1)在之前我的从C++容器中获取存储数据的类型-CSDN博客中,从容器中获取所存储的数据类型,就用到了std::decay,在函数func获取跟容器中一样数据类型,如下代码:

#include <iostream>
#include <boost/type_index.hpp>
 
template <typename Container>
void  func(Container& t, const char* pMessage) 
{
    using TYPE = std::decay<decltype(*t.begin())>;
    std::cout << pMessage << boost::typeindex::type_id_with_cvr<decltype(TYPE)>().pretty_name() << std::endl;
}

调用函数decltype(*t.begin()出来还是引用类型,通过std::decay退变成真正的原始类型Container::value_type。

2)示例如下:

#include <iostream>
#include <type_traits>
 
template <typename T, typename U>
struct decay_equiv : 
    std::is_same<typename std::decay<T>::type, U>::type 
{};
 
int main()
{
    std::cout << std::boolalpha
              << decay_equiv<int, int>::value << '\n'                    /*1*/
              << decay_equiv<int&, int>::value << '\n'                   /*2*/
              << decay_equiv<int&&, int>::value << '\n'                  /*3*/
              << decay_equiv<const int&, int>::value << '\n'             /*4*/
              << decay_equiv<int[2], int*>::value << '\n'                /*5*/
              << decay_equiv<int(int), int(*)(int)>::value << '\n';      /*6*/
}

上面的代码第1部分是传入int,就是普通类型,传出也是int

上面的代码第2部分是传入int&,通过std::decay擦除左值引用,传出int

上面的代码第3部分是传入int&&,通过std::decay擦除右值引用,传出int

上面的代码第4部分是传入const int&,通过std::decay擦除左值引用,去掉const,传出int

上面的代码第5部分是传入一维数组int[2],通过std::decay移除一个维度,退化传出int*

上面的代码第6部分是传入函数int(int),通过std::decay添加指针,传出指向传入函数的指针int(*)(int)。

6.注意事项

1、虽然std::decay可以处理许多类型的转换,但是它不能处理所有的类型。例如,std::decay不能处理类类型,枚举类型,和联合类型。

2、std::decay只能用于模板参数。如果我们尝试在非模板参数上使用std::decay,编译器将会报错。

3、std::decay不能用于消除指针类型。如果我们尝试在指针类型上使用std::decay,std::decay将不会有任何效果。

7.总结

std::decay是我们平时模版编程中使用的比较多的,在实际模板编程或者模板元编程中非常有用,在type_traits源代码里处处可见,实际工作中也会经常用到;上面的介绍只是抛砖引玉,要想真正掌握它,只有的不停地使用,在使用过程中遇到问题,解决问题,才能真正理解它的原理,灵活使用。

文章来源:https://blog.csdn.net/haokan123456789/article/details/135278508
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。