After much thought, the ideas posted by the other answers, and finding defects in the C++ standard I think I've got the solution that is as close as you can get to a compile-time check for the Swappable
concept.
It's not pretty. It uses a trick to detect if std::swap
is used by providing a function with the exact same signature as proposed by T.C.. Then we write helper functions to detect if swapping is possible at all, and whether it resolves to std::swap
. The last helper templates are used to see if std::swap
will be noexcept. This does not use the exact semantics as put forth in the C++14 standard, and assumes the what I think to be intended behaviour of swapping multidimensional arrays being noexcept
.
namespace detail {
namespace swap_adl_tests {
// if swap ADL finds this then it would call std::swap otherwise (same signature)
struct tag {};
template<class T> tag swap(T&, T&);
template<class T, std::size_t N> tag swap(T (&a)[N], T (&b)[N]);
// helper functions to test if an unqualified swap is possible, and if it becomes std::swap
template<class, class> std::false_type can_swap(...) noexcept(false);
template<class T, class U, class = decltype(swap(std::declval<T&>(), std::declval<U&>()))>
std::true_type can_swap(int) noexcept(
noexcept(swap(std::declval<T&>(), std::declval<U&>()))
);
template<class, class> std::false_type uses_std(...);
template<class T, class U>
std::is_same<decltype(swap(std::declval<T&>(), std::declval<U&>())), tag> uses_std(int);
template<class T>
struct is_std_swap_noexcept : std::integral_constant<bool,
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value
> { };
template<class T, std::size_t N>
struct is_std_swap_noexcept<T[N]> : is_std_swap_noexcept<T> { };
template<class T, class U>
struct is_adl_swap_noexcept : std::integral_constant<bool, noexcept(can_swap<T, U>(0))> { };
}
}
template<class T, class U = T>
struct is_swappable : std::integral_constant<bool,
decltype(detail::swap_adl_tests::can_swap<T, U>(0))::value &&
(!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value ||
(std::is_move_assignable<T>::value && std::is_move_constructible<T>::value))
> {};
template<class T, std::size_t N>
struct is_swappable<T[N], T[N]> : std::integral_constant<bool,
decltype(detail::swap_adl_tests::can_swap<T[N], T[N]>(0))::value &&
(!decltype(detail::swap_adl_tests::uses_std<T[N], T[N]>(0))::value ||
is_swappable<T, T>::value)
> {};
template<class T, class U = T>
struct is_nothrow_swappable : std::integral_constant<bool,
is_swappable<T, U>::value && (
(decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&
detail::swap_adl_tests::is_std_swap_noexcept<T>::value)
||
(!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&
detail::swap_adl_tests::is_adl_swap_noexcept<T, U>::value)
)
> {};