跟我学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在这其中起着重要的作用。通过它们几个整合应用,就可以实现一些重要的功能。另外,通过这些实现,也可以更好的理解和融会贯通元编程的知识。

相关推荐
天赐学c语言2 小时前
1.16 - 二叉树的中序遍历 && 动态多态的实现原理
数据结构·c++·算法·leecode
卜锦元2 小时前
EchoChat搭建自己的音视频会议系统01-准备工作
c++·golang·uni-app·node.js·音视频
ceclar1232 小时前
C++使用numeric
开发语言·c++
王老师青少年编程2 小时前
2024年12月GESP真题及题解(C++七级): 燃烧
c++·题解·真题·gesp·csp·七级·燃烧
汉克老师2 小时前
GESP2025年9月认证C++三级真题与解析(单选题9-15)
c++·算法·数组·string·字符数组·gesp三级·gesp3级
MLGDOU2 小时前
Chatsdk模型接口的设计
网络·c++
编程大师哥2 小时前
如何在C++中使用Redis的事务功能?
开发语言·c++·redis
朔北之忘 Clancy3 小时前
第二章 分支结构程序设计(1)
c++·算法·青少年编程·竞赛·教材·考级·讲义
汉克老师3 小时前
GESP2025年9月认证C++二级真题与解析(编程题2(菱形))
c++·找规律·二维数组·枚举算法·曼哈顿距离·模拟画图