Обнаружение поддержки оператора с помощью decltype / SFINAE - PullRequest
15 голосов
/ 30 апреля 2011

(несколько) устаревшая статья исследует способы использования decltype вместе с SFINAE для определения, поддерживает ли тип определенные операторы, такие как == или <.

Вот пример кода, чтобы определить, поддерживает ли класс оператор <:

template <class T>
struct supports_less_than
{
    static auto less_than_test(const T* t) -> decltype(*t < *t, char(0))
    { }

    static std::array<char, 2> less_than_test(...) { }

    static const bool value = (sizeof(less_than_test((T*)0)) == 1);
};

int main()
{
    std::cout << std::boolalpha << supports_less_than<std::string>::value << endl;
}

Это выводит true, поскольку, конечно, std::string поддерживает оператор <. Однако, если я пытаюсь использовать его с классом, который не поддерживает оператор <, я получаю ошибку компилятора:

error: no match for ‘operator<’ in ‘* t < * t’

Так что СФИНА здесь не работает. Я попробовал это на GCC 4.4 и GCC 4.6, и оба показали одинаковое поведение. Итак, возможно ли использовать SFINAE таким образом, чтобы определить, поддерживает ли тип определенные выражения?

Ответы [ 5 ]

16 голосов
/ 04 сентября 2013

В C ++ 11 самым коротким и общим решением, которое я нашел, было следующее:

#include <type_traits>

template<class T, class = decltype(std::declval<T>() < std::declval<T>() )> 
std::true_type  supports_less_than_test(const T&);
std::false_type supports_less_than_test(...);

template<class T> using supports_less_than = decltype(supports_less_than_test(std::declval<T>()));

#include<iostream>
struct random_type{};
int main(){
    std::cout << supports_less_than<double>::value << std::endl; // prints '1'
    std::cout << supports_less_than<int>::value << std::endl; // prints '1'
    std::cout << supports_less_than<random_type>::value << std::endl; // prints '0'
}

Работает с g++ 4.8.1 и clang++ 3.3


Aболее общее решение для произвольных операторов (ОБНОВЛЕНИЕ 2014)

Существует более общее решение, использующее тот факт, что все встроенные операторы также доступны (и, возможно, специализированы) через оболочки операторов STD, такие какstd::less (двоичный) или std::negate (одинарный).

template<class F, class... T, typename = decltype(std::declval<F>()(std::declval<T>()...))> 
std::true_type  supports_test(const F&, const T&...);
std::false_type supports_test(...);

template<class> struct supports;
template<class F, class... T> struct supports<F(T...)> 
: decltype(supports_test(std::declval<F>(), std::declval<T>()...)){};

Это может быть использовано в довольно общем виде, особенно в C ++ 14, где вычитание типа задерживается до вызова оболочки оператора («прозрачные операторы»).

Длябинарные операторы могут быть использованы как:

#include<iostream>
struct random_type{};
int main(){
    std::cout << supports<std::less<>(double, double)>::value << std::endl; // '1'
    std::cout << supports<std::less<>(int, int)>::value << std::endl; // '1'
    std::cout << supports<std::less<>(random_type, random_type)>::value << std::endl; // '0'
}

Для унарных операторов:

#include<iostream>
struct random_type{};
int main(){
    std::cout << supports<std::negate<>(double)>::value << std::endl; // '1'
    std::cout << supports<std::negate<>(int)>::value << std::endl; // '1'
    std::cout << supports<std::negate<>(random_type)>::value << std::endl; // '0'
}

(С стандартной библиотекой C ++ 11 немного сложнее, потому что нет ошибок наинсталируя decltype(std::less<random_type>()(...)), даже если для random_type не определена операция, можно реализовать прозрачные операторы вручную в C ++ 11, которые являются стандартными в C ++ 14)

Синтаксис довольно плавный.Я надеюсь, что что-то подобное будет принято в стандарте.


Два расширения:

1) Это работает для обнаружения приложений с необработанными функциями:

struct random_type{};
random_type fun(random_type x){return x;}
int main(){
    std::cout << supports<decltype(&fun)(double)>::value << std::endl; // '0'
    std::cout << supports<decltype(&fun)(int)>::value << std::endl; // '0'
    std::cout << supports<decltype(&fun)(random_type)>::value << std::endl; // '1'
}

2) Он может дополнительно определить, является ли результат конвертируемым / сопоставимым с определенным типом, в этом случае поддерживается double < double, но будет возвращено ложное время компиляции, поскольку результат не является указанным.

std::cout << supports<std::equal_to<>(std::result_of<std::less<>(double, double)>::type, random_type)>::value << std::endl; // '0'

Примечание. Я только что попытался скомпилировать код с C ++ 14 в http://melpon.org/wandbox/, и он не работал.Я думаю, что есть проблема с прозрачными операторами (например, std::less<>) в этой реализации (clang ++ 3.5 c ++ 14), поскольку, когда я реализую свой собственный less<> с автоматическим удержанием, он работает хорошо.

9 голосов
/ 30 апреля 2011

Вам нужно сделать свою функцию less_than_test шаблоном, поскольку SFINAE означает "Ошибка замены не является ошибкой", и в вашем коде нет функции шаблона, которая могла бы ошибиться при выборе.

template <class T>
struct supports_less_than
{
    template <class U>
    static auto less_than_test(const U* u) -> decltype(*u < *u, char(0))
    { }

    static std::array<char, 2> less_than_test(...) { }

    static const bool value = (sizeof(less_than_test((T*)0)) == 1);
};

int main()
{
    std::cout << std::boolalpha << supports_less_than<std::string>::value << endl;
}
6 голосов
/ 30 апреля 2011

Это C ++ 0x, нам больше не нужны трюки на основе sizeof ...; -]

#include <type_traits>
#include <utility>

namespace supports
{
    namespace details
    {
        struct return_t { };
    }

    template<typename T>
    details::return_t operator <(T const&, T const&);

    template<typename T>
    struct less_than : std::integral_constant<
        bool,
        !std::is_same<
            decltype(std::declval<T const&>() < std::declval<T const&>()),
            details::return_t
        >::value
    > { };
}

(Это основано на ответе iammilind, но нетребует, чтобы T operator< возвращаемый тип отличался от long long, и не требует, чтобы T был конструируемым по умолчанию.)

3 голосов
/ 30 апреля 2011

Ниже простой код удовлетворяет вашим требованиям (если вы не хотите, чтобы ошибка компиляции):

namespace supports {
  template<typename T>  // used if T doesn't have "operator <" associated
  const long long operator < (const T&, const T&);

  template <class T>
  struct less_than {
    T t;
    static const bool value = (sizeof(t < t) != sizeof(long long));
  };  
}

Использование:

supports::less_than<std::string>::value ====> true;  // ok
supports::less_than<Other>::value ====> false;  // ok: no error

[Примечание: если вы хотите, чтобы ошибка компиляции для классовне имея operator <, что очень легко создать с помощью всего лишь нескольких строк кода.]

0 голосов
/ 30 апреля 2011

@ xDD действительно верен, хотя его пример немного ошибочен.

Это компилируется на ideone:

#include <array>
#include <iostream>

struct Support {}; bool operator<(Support,Support) { return false; }
struct DoesNotSupport{};

template <class T>
struct supports_less_than
{
  template <typename U>
  static auto less_than_test(const U* u) -> decltype(*u < *u, char(0))
  { }

  static std::array<char, 2> less_than_test(...) { }

  static const bool value = (sizeof(less_than_test((T*)0)) == 1);
};

int main()
{
  std::cout << std::boolalpha << supports_less_than<Support>::value << std::endl;
  std::cout << std::boolalpha <<
     supports_less_than<DoesNotSupport>::value << std::endl;
}

И приводит к:

true
false

См. это здесь в действии.

Дело в том, что SFINAE применяется только к шаблону функции .

...