Wykrywanie obsługi operatora za pomocą decltype/SFINAE

A (nieco) przestarzały Artykuł bada sposoby użycia decltype wraz z SFINAE do wykrywania, czy typ obsługuje pewne operatory, takie jak == lub <.

Oto przykładowy kod do wykrycia, czy klasa obsługuje operator <:

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

To wyjście true, ponieważ oczywiście std::string obsługuje operator <. Jednak, jeśli spróbuję użyć go z klasą, która nie obsługuje operatora <, dostaję błąd kompilatora:

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

Więc SFINAE jest nie pracuję tutaj. Próbowałem tego na GCC 4.4 i GCC 4.6 i oba wykazywały to samo zachowanie. Czy możliwe jest użycie SFINAE w ten sposób, aby wykryć, czy typ obsługuje pewne wyrażenia?

Author: Xeo, 2011-04-30

5 answers

Musisz uczynić swoją funkcję less_than_test szablonem, ponieważ SFINAE oznacza Substitution Failure nie jest błędem i nie ma funkcji szablonu, która może zawieść wybór w Twoim kodzie.

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;
}
 9
Author: xDD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-30 04:53:25

W C++11 najkrótszym najbardziej ogólnym rozwiązaniem jakie znalazłem było to:

#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'
}

Współpracuje z g++ 4.8.1 i clang++ 3.3


Bardziej ogólne rozwiązanie dla dowolnych operatorów (aktualizacja 2014)

Istnieje bardziej ogólne rozwiązanie, które wykorzystuje fakt, że wszystkie wbudowane operatory są również dostępne (i optycznie wyspecjalizowane) poprzez owijarki operatorów STD, takie jak std::less (binary) lub std::negate (uniary).

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

To może być używane w dość ogólny sposób, szczególnie w C++14, gdzie dedukcja typu jest opóźniona do wywołania wrappera operatora ("transparent operations").

Dla operatorów binarnych może być używany jako:

#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'
}

Dla operatorów jednoargumentowych:

#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'
}

(z biblioteką standardową C++11 jest to nieco bardziej skomplikowane, ponieważ nie ma awarii przy instalowaniu decltype(std::less<random_type>()(...)) nawet jeśli nie ma operacji zdefiniowanej dla random_type, można zaimplementować ręcznie przezroczyste operatory w C++11, które są standardem w C++14)

Składnia jest całkiem gładko. Mam nadzieję, że coś takiego zostanie przyjęte w standardzie.


Dwa rozszerzenia:

1) działa do wykrywania aplikacji funkcji surowych:

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) może dodatkowo wykryć, czy wynik jest zamienny / porównywalny do określonego typu, w tym przypadku double < double jest wspierany, ale zostanie zwrócony fałsz w czasie kompilacji, ponieważ wynik nie jest określony.

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

Uwaga: właśnie próbowałem skompilować kod z C++14 w http://melpon.org/wandbox i nie zadziałało. Myślę, że jest problem z operatorami transparentnymi (jak std::less<>) w tej implementacji( clang++ 3.5 c++14), od kiedy implementuję własne less<> z automatyczną dedukcją to działa dobrze.

 15
Author: alfC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-06 03:51:10

To jest C++0x, nie potrzebujemy już sztuczek opartych na 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
    > { };
}

(jest to oparte na odpowiedzi iammilind, ale nie wymaga, aby T'S operator< return-type był różny rozmiar niż long long i nie wymaga, aby T były domyślnie konstruowalne.)

 6
Author: ildjarn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-06 04:45:55

Poniżej prosty kod spełnia Twoje wymagania (jeśli nie chcesz błędu kompilacji):

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

Użycie:

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

[Uwaga: Jeśli chcesz skompilować błąd dla klas, które nie mają operator <, to bardzo łatwo jest wygenerować go za pomocą kilku linii kodu.]

 3
Author: iammilind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-30 05:33:23

@XDD rzeczywiście ma rację, choć jego przykład jest nieco błędny.

To kompiluje się na 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;
}

I daje:

true
false

Zobacz tutaj w akcji.

Chodzi o to, że SFINAE stosuje się tylko do funkcji szablonu .

 0
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-30 10:06:45