Каков правильный способ реализации is_swappable для тестирования концепции Swappable?

Я бы рассмотрел "правильную" реализацию для is_swappable следующим образом:

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

is_swappable наследует от std::true_type, если T и U Swappable, в противном случае от std::false_type.


Я пробовал много вещей, но SFINAE просто не работает. Это особенно неприятный контрпример:

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

Ясно, что A не Swappable. Однако любое общее решение, которое я могу придумать, неправильно обрабатывает приведенный выше пример.

Реализация SFINAE Я попытался, но не работал, выглядел так:

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_swappable без помощи компилятора?

Ответы

Ответ 1

После долгих размышлений идеи, опубликованные другими ответами, и обнаружение дефектов в стандарте С++ Я думаю, что у меня есть решение, которое находится так близко, как вы может получить проверку времени компиляции для концепции Swappable.

Это некрасиво. Он использует трюк, чтобы определить, используется ли std::swap, предоставляя функцию с той же самой сигнатурой, что предложено T.C.. Затем мы записываем вспомогательные функции, чтобы определить, возможно ли замена, и разрешает ли оно std::swap. Последние вспомогательные шаблоны используются, чтобы увидеть, не будет ли std::swap noexcept. Это не использует точную семантику, как указано в стандарте С++ 14, и предполагает, что я думаю, что это будет предполагаемое поведение подкачки многомерных массивов, являющихся 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)
    )
> {};

Ответ 2

Основываясь на ответе @jrok, мы можем сказать, вызовет ли неквалифицированный вызов swap std::swap, написав функцию swap с той же сигнатурой, что и std::swap, но будет уникальным возвращаемым типом, который затем может быть проверен:

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 { };
}

Тогда определение is_swappable становится:

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))
    > { };

Нам также нужен специальный случай для подкачки массивов:

template<typename T, std::size_t N>
struct is_swappable<T[N]> : is_swappable<T> {};

Ответ 3

Вот мой пример:

#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>{};
}

Причина, по которой ваша работа не работает, заключается в том, что она проверяет, нормально ли вызывать swap, но не будет ли она компилироваться, если она была создана. Это из сферы СФИНА (не непосредственный контекст).

Итак, я просто расширил тест с помощью требований для std::swap, то есть - T должен быть MoveAssignable и MoveConstructible.