跟我学C++中级篇—std::is_swappable手动实现

一、说明

对象的交换在C++开发中非常常见,比如一些常见的排序算法中以及面试中字符串手动实现中都可以使用,典型的就是STL中的std::swap。如果在普通的编程中,交换两个对象还好控制。如果在模板编程中呢?可能一大片错误遮蔽了屏幕,这个有点不太合适。所以是不是可以增加一个类似于前面的is_xxx系列来判断类型对象不是可以交换呢?

二、std::is_swappable

C++17中为了确保在交换对象时的安全性和通用性,提供了一个元编程接口std::is_swappable用来在编译期判断当前的对象是否可以被交换。其返回一个布尔值,用来表示该类型对象是否可交换。其定义如下:

c 复制代码
template< class T, class U >
struct is_swappable_with;
template< class T >
struct is_swappable;
template< class T, class U >
struct is_nothrow_swappable_with;
template< class T >
struct is_nothrow_swappable;

实现的代码如下:

c++ 复制代码
    struct __do_is_swappable_impl
    {
      template<typename _Tp, typename
               = decltype(swap(std::declval<_Tp&>(), std::declval<_Tp&>()))>
        static true_type __test(int);

      template<typename>
        static false_type __test(...);
    };
  template<typename _Tp>
    struct __is_swappable_impl
    : public __swappable_details::__do_is_swappable_impl
    {
      typedef decltype(__test<_Tp>(0)) type;
    };
  /// is_swappable
  template<typename _Tp>
    struct is_swappable
    : public __is_swappable_impl<_Tp>::type
    {
      static_assert(std::__is_complete_or_unbounded(__type_identity<_Tp>{}),
	"template argument must be a complete class or an unbounded array");
    };

这里先不对这些代码说明,等和下面的代码实现对比着分析就更明白了。

需要说明的是,标准库中还提供了is_nothrow_swappable、std::is_swappable_with和is_nothrow_swappable_with三个类似的元编程接口,std::is_swappable_with是用来比较两个类型是否可以交换,而std::is_swappable是当前一个类型是否可以交换。

三、源码实现

下面就实现一个普通代码的版本:

c 复制代码
#include <type_traits>
#include <utility>

// swap
using std::swap;
template <typename T, typename U, typename = void> struct swappable_with_impl : std::false_type {};
template <typename T, typename U>
struct swappable_with_impl<T, U,
                           std::void_t<decltype(swap(std::declval<T &>(), std::declval<U &>())), decltype(swap(std::declval<U &>(), std::declval<T &>()))>>
    : std::true_type {};

template <typename T, typename U> struct is_nothrow_swappable_with_impl {
private:
  template <typename TT, typename UU>
  static auto test(int)
      -> std::integral_constant<bool, noexcept(swap(std::declval<TT &>(), std::declval<UU &>())) &&noexcept(swap(std::declval<UU &>(), std::declval<TT &>()))>;
  template <typename, typename> static std::false_type test(...);
public:
  using type = decltype(test<T, U>(0));
};

// is_swappable_with
template <typename T, typename U> struct is_swappable_with : swappable_with_impl<T, U> {};
template <typename T, typename U> inline constexpr bool is_swappable_with_v = is_swappable_with<T, U>::value;

// is_swappable
template <typename T> struct is_swappable : is_swappable_with<T, T> {};
template <typename T> inline constexpr bool is_swappable_v = is_swappable<T>::value;

// is_nothrow_swappable_with
template <typename T, typename U> struct is_nothrow_swappable_with : is_nothrow_swappable_with_impl<T, U>::type {};
template <typename T, typename U> inline constexpr bool is_nothrow_swappable_with_v = is_nothrow_swappable_with<T, U>::value;

// is_nothrow_swappable
template <typename T> struct is_nothrow_swappable : is_nothrow_swappable_with<T, T> {};
template <typename T> inline constexpr bool is_nothrow_swappable_v = is_nothrow_swappable<T>::value;

再看一个高版本的实现:

c 复制代码
#include <type_traits>
#include <utility>

// 检查 swap 是否有效
template<typename T, typename U, typename = void>
struct is_swappable_with_impl : std::false_type {};
    
template<typename T, typename U>
struct is_swappable_with_impl<T, U,
   std::void_t<decltype(
       //
       std::declval<void(&)(T&, U&) noexcept(noexcept(
           swap(std::declval<T&>(), std::declval<U&>())
       ))>(),
       std::declval<void(&)(U&, T&) noexcept(noexcept(
           swap(std::declval<U&>(), std::declval<T&>())
       ))>()
   )>
> : std::true_type {};

// 主模板
template<typename T, typename U = T>
struct is_swappable : is_swappable_with_impl<T, U> {};

// 辅助变量模板
template<typename T, typename U = T>
inline constexpr bool is_swappable_v = is_swappable<T, U>::value;

测试的代码:

c 复制代码
#include <iostream>

struct SwapOK {
  int d;
};

void swap(SwapOK &a, SwapOK &b) { std::swap(a.d, b.d); }

struct SwapErr {
  SwapErr(const SwapErr &) = delete;
  int d;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << "SwapOK is: " << is_swappable_v<SwapOK> << std::endl;
  std::cout << "SwapErr is: " <<is_swappable_v<SwapErr> << std::endl;

  std::cout << "int and short : " << is_swappable_with_v<int, short> << std::endl;
  std::cout << "STL int and double : " << std::is_swappable_with_v<int, double> << std::endl;
  return 0;
}

代码的原理就是在编译时构造一个swap(std::swap)调用,如果这个调用是合法的,则std::is_swappable::value为true,反之为false。上面的代码中仍然使用了std::void_t的处理来控制类型T(或U)可以用于swap(如果自定义类需要手动实现swap)。具体使用decltype和declval来获取具体的类型进行判断(须满足交换律)。至于noexcept版本,只是增加对函数的noexcept控制实现即可。来获取其匹配的方法仍然是编译适配的为std::true_type,进而展开生成正确的返回值;否则直接返回std::false_type。

另外需要说明的是,上面的代码在实际应用时,建议增加相关的名空间控制,否则很容易和STL中的相关代码混淆,产生误判。

四、总结

这几篇针对元编程接口的实现,可以发现std::true_type和std::void_t在这其中起着重要的作用。通过它们几个整合应用,就可以实现一些重要的功能。另外,通过这些实现,也可以更好的理解和融会贯通元编程的知识。

相关推荐
历程里程碑16 分钟前
Linux 库
java·linux·运维·服务器·数据结构·c++·算法
Sheep Shaun18 分钟前
如何让一个进程诞生、工作、终止并等待回收?——探索Linux进程控制与Shell的诞生
linux·服务器·数据结构·c++·算法·shell·进程控制
小龙报34 分钟前
【51单片机】从 0 到 1 玩转 51 蜂鸣器:分清有源无源,轻松驱动它奏响新年旋律
c语言·数据结构·c++·stm32·单片机·嵌入式硬件·51单片机
石去皿1 小时前
【嵌入式就业6】计算机组成原理与操作系统核心机制:夯实底层基础
c++·面试·嵌入式
王老师青少年编程1 小时前
2024年信奥赛C++提高组csp-s初赛真题及答案解析(完善程序第1题)
c++·题解·真题·初赛·信奥赛·csp-s·提高组
一只小小的芙厨1 小时前
AT_tkppc3_d 巨大チェスボード 题解
c++·题解
我在人间贩卖青春1 小时前
C++之继承与派生类的关系
c++·向上造型·向下造型
Trouvaille ~1 小时前
【Linux】应用层协议设计实战(二):Jsoncpp序列化与完整实现
linux·运维·服务器·网络·c++·json·应用层
EmbedLinX2 小时前
嵌入式之协议解析
linux·网络·c++·笔记·学习
wangjialelele2 小时前
Linux中的进程管理
java·linux·服务器·c语言·c++·个人开发