Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a proper way to implement is_swappable to test for the Swappable concept?

Tags:

c++

I would consider "proper" implementation for is_swappable to be the following:

template<class T, class U = T> struct is_swappable<T, U> : /* see below */ { }

is_swappable inherits from std::true_type if T and U are Swappable, otherwise from std::false_type.


I have tried many things, but SFINAE just doesn't seem to work. This is a particularly nasty counterexample:

struct A {
    A() {}
    ~A() {}
    A(const A&) = delete;
    A(A&&) = delete;
};

Clearly A is not Swappable. Yet any generic solution I can come up with does not properly handle the above example.

A SFINAE implementation I have tried, but did not work looked like this:

namespace with_std_swap {
    using std::swap;

    template<class T, class U, class =
        decltype(swap(std::declval<T&>(), std::declval<U&>()))>
    std::true_type swappable_test(int);

    template<class, class> std::false_type swappable_test(...);
}

template<class T, class U = T>
struct is_swappable
: decltype(with_std_swap::using_std_swap::swappable_test<T, U>(0)) { };

Is there any way to code is_swappable without compiler help?

like image 262
orlp Avatar asked Nov 04 '14 20:11

orlp


3 Answers

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> {};
like image 51
T.C. Avatar answered Nov 07 '22 00:11

T.C.


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.

like image 28
jrok Avatar answered Nov 07 '22 00:11

jrok


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)
    )
> {};
like image 42
orlp Avatar answered Nov 07 '22 02:11

orlp