Я пытаюсь решить относительно простую экскурсию с участием SFINAE.
Моя цель - найти лучший способ сортировки для определенного типа T.
Для меня есть 3 случая:

  1. type T supports `sort` function
  2. type T supports range i.e. have begin and end functions (lets not include comparability at this point)
  3. type T is not sortable (doesn't support ranges and doesn't have sort function)

Итак, я написал базовую перегрузку шаблона и попытался извлечь выгоду из SFINAE.

#include <iostream>
#include <vector>

struct HaveSort { char c; };
struct HaveRange { char c; HaveSort s; };
struct HaveNone { char c; HaveRange r; };

template<typename T>
HaveSort test_sort(decltype(&T::sort), decltype(&T::sort));

template<typename T>
HaveRange test_sort(decltype(&T::begin), decltype(&T::end));

template<typename T>
HaveNone test_sort(...);

template<typename T, int N>
struct sort_helper;

template<typename T>
struct sort_helper<T, sizeof(HaveSort)>
{
    static void fsort(T& x)
    {
        std::cout << "Type " << typeid(x).name() << " supports sort" << std::endl;
        x.sort();
    }
};

template<typename T>
struct sort_helper<T, sizeof(HaveRange)>
{
    static void fsort(T& x)
    {
        std::cout << "Type " << typeid(x).name() << " supports range" << std::endl;
        std::sort(x.begin(), x.end());
    }
};

template<typename T>
struct sort_helper<T, sizeof(HaveNone)>
{
    static void fsort(T& x)
    {
        std::cout << "Type " << typeid(x).name() << " supports nothing" << std::endl;
    }
};

template<typename T>
void fast_sort(T& x)
{
    sort_helper<T, sizeof(test_sort<T>(NULL, NULL))>::fsort(x);
}


class A {};
class B { void sort() {} };

int main()
{
    static_assert(sizeof(HaveSort) != sizeof(HaveRange), "Find other way to handle HaveSort and HaveRange\n");
    static_assert(sizeof(HaveRange) != sizeof(HaveNone), "Find other way to handle HaveRange and HaveNone\n");

    std::vector<int> V{ 1,9,5,3 };
    A a;
    B b;
    fast_sort(V);
    fast_sort(a);
    fast_sort(b);
}

Это выводит

Type class std::vector<int,class std::allocator<int> > supports nothing
Type class A supports nothing
Type class B supports nothing

Для всех трех классов - vector<int>, A, B.

Кто-нибудь знает, почему СФИНАЭ здесь не подхватывает сразу перегрузки?
Заранее спасибо.

1
Eduard Rostomyan 10 Сен 2021 в 13:45

2 ответа

Лучший ответ

Фактически SFINAE применимы ко всем вашим типам:

  • A не имеет sort() / begin() / end()
  • B не имеет begin() / end() и не имеет public sort().
  • std::vector<int> не имеет sort(), а &std::vector<int>::begin (аналогично end) неоднозначен, поскольку есть несколько перегрузок (const и не - const метод).

Я бы сделал что-то подобное:

template <std::size_t N> struct overload_priority : overload_priority<N - 1> {};
template <> struct overload_priority<0> {}; // lowest priority


template<typename T>
auto fsort(T& x, overload_priority<2>) -> decltype(x.sort(), void())
{
    std::cout << "Type " << typeid(x).name() << " supports sort" << std::endl;
    x.sort();
}

template<typename T>
auto fsort(T& x, overload_priority<1>) -> decltype(std::sort(x.begin(), x.end()), void())
{
    std::cout << "Type " << typeid(x).name() << " supports range" << std::endl;
    std::sort(x.begin(), x.end());
}

template<typename T>
void fsort(T& x, overload_priority<0>)
{
    std::cout << "Type " << typeid(x).name() << " supports nothing" << std::endl;
}

template<typename T>
void fast_sort(T& x)
{
    fsort(x, overload_priority<5>{}); // big enough
}

Демо

3
Jarod42 10 Сен 2021 в 11:11

Причины вашего отказа SFINAE:

  • Функция-член sort() - private

  • &T::begin имеет несколько перегрузок, поэтому получить его адрес не удается.

Вместо того, чтобы брать адреса и сравнивать размеры, я бы использовал bool и некоторые помощники SFINAE.

template<class T>
struct has_sort { // check if T has a (public) member function called sort()
    template<class> static auto Test(...) -> std::false_type;

    template<class TT>
    static auto Test(int) -> 
        decltype( std::declval<TT&>().sort(), std::true_type() );

    static constexpr bool value = decltype(Test<T>(0))::value;
};

template<class T> inline static constexpr bool has_sort_v = has_sort<T>::value;
template<class T>
struct has_range {  // check if T supports `std::begin` and `std::end`
    template<class> static auto Test(...) -> std::false_type;

    template<class TT>
    static auto Test(int) ->
        decltype( std::begin(std::declval<TT&>()),
                  std::end(std::declval<TT&>()),
                  std::true_type() );

    static constexpr bool value = decltype(Test<T>(0))::value;
};

template<class T> inline static constexpr bool has_range_v = has_range<T>::value;

Тогда ваша функция fast_sort может выглядеть так:

#include <algorithm>
#include <iostream>
#include <vector>

template<typename T>
void fast_sort(T& x) {
    if constexpr(has_sort_v<T>) {
        std::cout << "has_sort\n";
        x.sort();

    } else if constexpr(has_range_v<T>) {
        std::cout << "has_range\n";
        std::sort(std::begin(x), std::end(x));

    } else {
        std::cout << "bohoo\n";
    }
}

class A {};
class B { public: void sort() {} }; // note: sort() made public

int main() {
    std::vector<int> V{ 1,9,5,3 };
    A a;
    B b;
    fast_sort(V);
    fast_sort(a);
    fast_sort(b);
}

Выход:

has_range
bohoo
has_sort

Демо

3
Ted Lyngmo 10 Сен 2021 в 12:23