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

相关推荐
Tairitsu_H1 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现
HugoStudio_SWAN1 小时前
洛谷 B4450 / B3867 / B3923 智慧购物、储蓄与做题——从程序到生活
c++·学习·程序人生·算法·生活
会周易的程序员3 小时前
5Draft(五帝)测试报告
服务器·c++·分布式·raft·共识算法·共识·算力服务器
UIU1143 小时前
scanf与cout的误区:探究其内部的机制
c++·学习·c#·scanf
闻缺陷则喜何志丹3 小时前
【栈 运算系统】P3719 [AHOI2017初中组] rexp|普及+
c++·算法··洛谷·运算系统
bnmoel4 小时前
C++ 基础入门篇(一):命名空间,输入&输出,缺省参数,函数重载
c++·函数重载·语法·命名空间·缺省参数
Zixhy4 小时前
多点巡检机器人项目技术总结
linux·c++·机器人·自动驾驶
Pointer Pursuit6 小时前
C++11特性(二)
前端·c++·算法
tudousisi2227 小时前
CSP 第3题训练 Day 5|CSP202509C HTTP 头信息|Blog A
c++
闻缺陷则喜何志丹8 小时前
【动态规划】P10726 [GESP202406 八级] 空间跳跃|普及+
c++·算法·动态规划·洛谷