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