What is a proper way to implement is_swappable to test for the Swappable concept?
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)
)
> {};
Here's my take on this:
#include <iostream>
#include <type_traits>
#include <utility>
namespace detail {
using std::swap;
template<typename T>
struct can_call_swap_impl {
template<typename U>
static auto check(int)
-> decltype( swap(std::declval<T&>(), std::declval<T&>()),
std::true_type());
template<typename>
static std::false_type check(...);
using type = decltype(check<T>(0));
};
template<typename T>
struct can_call_swap : can_call_swap_impl<T>::type { };
}
template<typename T>
struct is_swappable :
std::integral_constant<bool,
detail::can_call_swap<T>::value &&
std::is_move_assignable<T>::value &&
std::is_move_constructible<T>::value
> { };
struct A
{
A() {}
~A() {}
A(const A&) = delete;
A(A&&) = delete;
};
int main()
{
std::cout << is_swappable<A>{};
}
The reason yours doesn't work is that it only checks whether it is ok to call swap
, not whether it would actualy compile if it were instantiated. That's out of realm of SFINAE (not immediate context).
So I just extended the test with the requirements for std::swap
, that is - T
must be MoveAssignable
and MoveConstructible
.
Building on @jrok's answer, we can tell if an unqualified swap
call will call std::swap
by writing a swap
function with the same signature as std::swap
but a unique return type that can then be examined:
namespace detail2 {
struct tag {};
template<class T>
tag swap(T&, T&);
template<typename T>
struct would_call_std_swap_impl {
template<typename U>
static auto check(int)
-> std::integral_constant<bool, std::is_same<decltype( swap(std::declval<U&>(), std::declval<U&>())), tag>::value>;
template<typename>
static std::false_type check(...);
using type = decltype(check<T>(0));
};
template<typename T>
struct would_call_std_swap : would_call_std_swap_impl<T>::type { };
}
Then the definition of is_swappable
becomes:
template<typename T>
struct is_swappable :
std::integral_constant<bool,
detail::can_call_swap<T>::value &&
(!detail2::would_call_std_swap<T>::value ||
(std::is_move_assignable<T>::value &&
std::is_move_constructible<T>::value))
> { };
We also need a special case for swapping arrays:
template<typename T, std::size_t N>
struct is_swappable<T[N]> : is_swappable<T> {};