跟我学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 小时前
面向对象设计原则——C++
开发语言·c++·数码相机
syagain_zsx19 小时前
jsoncpp 使用指南:编译链接、Json::Value 取值与高频坑
c++·json·库·http格式
布莱克60520 小时前
理解 = delete 与 = default:两种赋值语义的差异
c++
小小、码农21 小时前
[特殊字符]【C++11】类的新功能全面解析:移动构造、移动赋值、default、delete、final 与 override
linux·开发语言·网络·c++
库玛西1 天前
外部排序全解析:败者树、置换-选择排序与最佳归并树
c语言·数据结构·c++·笔记·考研·算法
General_G1 天前
创建型模式—原型(Prototype)模式
c++·设计模式·原型模式
syagain_zsx1 天前
protobuf 实战:从 .proto 到 C++ 序列化的完整链路
c++·学习·程序人生·谷歌·protobuf
welfare9621 天前
新号别搞:C++_list
开发语言·c++
xiaoye-duck1 天前
《C++ 项目:手写大模型接入 SDK》云端 API 调用基础与 Apifox 调试实战
网络·c++·语言模型
坤坤子吖1 天前
Linux 线程同步与互斥——条件变量与线程同步
linux·开发语言·c++·笔记·学习