2011-04-30 13 views
13

A (un poco) anticuado article explora maneras de utilizar decltype junto con SFINAE para detectar si un tipo compatible con ciertos operadores, como == o <.Detect soporte del operador con decltype/SFINAE

Aquí es código de ejemplo para detectar si una clase es compatible con el operador <:

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

Esto da salida a true, ya que por supuesto std::string soporta el operador <. Sin embargo, si trato de usarlo con una clase que no significa apoyar el operador <, me sale un error de compilación:

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

Así SFINAE no funciona aquí. Intenté esto en GCC 4.4 y GCC 4.6, y ambos exhibieron el mismo comportamiento. Entonces, ¿es posible usar SFINAE de esta manera para detectar si un tipo admite ciertas expresiones?

+0

No necesitamos usar 'C++ 0x' para comprobar si existe la función' operator <'en la clase. Podemos simplemente personalizar esa función para la sobrecarga genérica y usar su tamaño para la lógica negativa. Ver mi respuesta a continuación. – iammilind

+0

Para aquellos que buscan una solución portátil preempaquetada para esto, hay 'plantilla struct has_less: public true_type-or-false_type {};' in '#include '. Documentación: http://www.boost.org/doc/libs/1_56_0/libs/type_traits/doc/html/boost_typetraits/reference/has_less.html – alfC

Respuesta

9

Debe hacer que su función less_than_test sea una plantilla, ya que SFINAE significa falla de sustitución, no es un error y no hay función de plantilla que pueda fallar en la selección de su código.

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

Pero este código aún arroja un error, si una clase no tiene 'operator <'. Interlocutor acaba de mencionar solo el código compilado. Y creo que has pegado lo mismo. – iammilind

+0

Publiqué una versión corregida y completa del código a continuación (porque detestaba editar su publicación tan a fondo). Puede usarlo para corregir su ejemplo, y en este caso eliminaré mi respuesta. –

+0

@iammilind: vea mi respuesta, funciona, tal como se probó en ideone en http://ideone.com/a68WO –

3

A continuación código simple satisface sus necesidades (si no desea compilar error):

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

Uso:

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

[Nota: Si desea error de compilación para las clases no tener operator < que es muy fácil de generar con muy pocas líneas de código.]

+3

Técnicamente, si un tipo sobrecarga 'operator <' y pasa a dar un tipo del mismo tamaño que 'long long'' [o si' sizeof (bool) == sizeof (long long) '], esto podría producir un falso negativo . En tal caso, la sobrecarga probablemente no se esté utilizando como comparador de todos modos, pero vale la pena tenerlo en cuenta. –

+0

Esto también requiere que un tipo sea constructable por defecto. Estos no son solo temas que vale la pena conocer, sino que también son completamente triviales para solucionar ... – ildjarn

+0

@ildjarn, ¿por qué votar abajo? Comprobé mi código. Funciona incluso si el tipo no es construible por defecto. ¿Puedes señalar el problema, por favor? – iammilind

0

@xDD de hecho es correcto, aunque su ejemplo es ligeramente erróneo.

Esto compila en 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; 
} 

y da como resultado:

true 
false 

Ver it here en acción.

El punto es que SFINAE solo se aplica a las funciones de la plantilla .

+0

@Matthieu, incluso mi respuesta también funciona correctamente. Pero OP ha decidido aceptar la primera respuesta. :) – iammilind

+1

@iammilind: el suyo funciona por efecto secundario, ya que depende de un tamaño específico para el tipo de retorno de 'operator <'. Admito que debería funcionar en todos los casos prácticos :) –

+0

No veo un obvio diferente al código en mi respuesta. ¿Te refieres a las directivas de inclusión agregadas? Si es así, los dejé porque quedaron fuera en la pregunta también. – xDD

6

Este es C++ 0x, no necesitamos sizeof trucos basados ​​en cualquier mas ...; -]

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

(Esto se basa en la respuesta de iammilind, pero no requiere que Toperator< return-type tiene un tamaño diferente de long long y no requiere que T sea constructable por defecto.)

+0

FYI, también hay 'std :: declval ()' que puede usar en lugar de su propio 'details :: make ()'. –

+0

@pyrtsa: Ah, de hecho, tiendo a olvidarme de 'declive' ya que no está en VC++ 2010. Voy a editar. – ildjarn

+0

@ldjarn: para aquellos de nosotros que usamos VC++ 2010, ¿te importaría volver a poner los detalles :: make () versión también? – ForeverLearning

14

En C++ 11 la más corta solución más general que encontré fue ésta:

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

Works con g++ 4.8.1 y clang++ 3.3


una solución más general para los operadores arbitrarios (ACTUALIZACIÓN 2014)

Hay una solución más general que explota el hecho de que todos los edificios Los operadores t-in también son accesibles (y posiblemente especializados) a través de contenedores de operador STD, como std::less (binario) o std::negate (unario).

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

Esto se puede utilizar de una manera muy general, especialmente en C++ 14, donde el tipo de deducción se retrasa a la llamada envoltura operador ("operadores transparentes").

Para los operadores binarios que puede ser utilizado como:

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

Para los operadores unarios:

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

(Con la biblioteca estándar de C++ 11 es un poco más complicado porque no hay fracaso al instalar decltype(std::less<random_type>()(...)), incluso si no hay una operación definida para random_type, se pueden implementar operadores transparentes manualmente en C++ 11, que son estándar en C++ 14)

La sintaxis es bastante fluida. Espero que algo así se adopte en el estándar.


Dos extensiones:

1) Se trabaja para detectar aplicaciones-función en bruto:

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) que puede detectar, además, si el resultado es convertible/comparable a un cierto tipo, en este caso es compatible con double < double, pero se devolverá un tiempo de compilación falso porque el resultado no es el especificado.

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

Nota: Me acaba de intentar compilar el código C++ con 14 en http://melpon.org/wandbox/ y no funcionó. Creo que hay un problema con los operadores transparentes (como std::less<>) en esa implementación (clang ++ 3.5 C++ 14), ya que cuando implemente mi propio less<> con deducción automática, funciona bien.

+0

+1: Este es mi favorito personal. Es corto y funciona bien. Tenga en cuenta que el ejecutable 'C++' es en realidad un alias para 'g ++', si está en Linux. – refi64

+0

¿Alguna posibilidad de aplicar esto al miembro acceder 'operator->'? – Walter