2010-12-03 7 views
7

El siguiente código de la mina debería detectar si tiene Tbegin y end métodos:SFINAE compilador preocupa

template <typename T> 
struct is_container 
{ 
    template <typename U, typename U::const_iterator (U::*)() const, 
          typename U::const_iterator (U::*)() const> 
    struct sfinae {}; 

    template <typename U> static char test(sfinae<U, &U::begin, &U::end>*); 
    template <typename U> static long test(...); 

    enum { value = (1 == sizeof test<T>(0)) }; 
}; 

Y he aquí algo de código de prueba:

#include <iostream> 
#include <vector> 
#include <list> 
#include <set> 
#include <map> 

int main() 
{ 
    std::cout << is_container<std::vector<std::string> >::value << ' '; 
    std::cout << is_container<std::list<std::string> >::value << ' '; 
    std::cout << is_container<std::set<std::string> >::value << ' '; 
    std::cout << is_container<std::map<std::string, std::string> >::value << '\n'; 
} 

En g ++ 4.5.1, la salida es 1 1 1 1. En Visual Studio 2008, sin embargo, la salida es 1 1 0 0. ¿Hice algo mal, o es simplemente un error de VS 2008? ¿Alguien puede probar en un compilador diferente? ¡Gracias!

+0

Funcionó para mí en MinGW g ++ 4.4.0 (obtuvo '1 1 1 1'). Desafortunadamente no tengo idea de por qué falla en VS2008, aunque el código parece correcto. –

+0

Lo mismo en VS2010 '1 1 0 0'. Tenía la corazonada de que podría estar depurando STL, así que probé '/ DDEBUG' y'/DNDEBUG' pero no hizo ninguna diferencia. – Rup

+1

Puede echar un vistazo a la instalación ['HAS_XXX'] (http://live.boost.org/doc/libs/1_34_0/boost/mpl/has_xxx.hpp) proporcionada por Boost.MPL para ver cómo funcionan. capacidades SFINAE limitadas de ciertos compiladores. –

Respuesta

1

Stephan T. Lavavej tiene this decir:

Tenga en cuenta que es técnicamente prohibido tomar la dirección de una función miembro de la biblioteca estándar . (Pueden ser sobrecargado, lo que hace &foo::bar ambigua, y pueden tener argumentos por omisión adicionales, derrotar los intentos para eliminar la ambigüedad a través de static_cast.)

así que supongo que voy a usar la versión más simple que sólo comprueba la anidado const_iterator tipo.

12

Así que, aquí es cómo voy a depurar estas cosas.

Primero, comente la alternativa negativa para que obtenga un error en lugar de una discrepancia. A continuación, intente crear una instancia del tipo que está poniendo en la función con uno de los elementos que no funcionan.

En este paso, pude crear una instancia de su objeto sfinae pero todavía no funcionaba. Esto me permite saber que ES un error de VS, por lo que la pregunta es cómo solucionarlo.

VS parece tener problemas con SFINAE cuando está hecho como está. Funciona mejor cuando envuelve su objeto sfinae. Lo hice así:

template <typename U, typename it_t = typename U::const_iterator > 
struct sfinae 
{ 
    // typedef typename U::const_iterator it_t; - fails to compile with non-cont types. Not sfinae 
    template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const > 
    struct type_ {}; 

    typedef type_<U,it_t,&U::begin,&U::end> type; 
}; 

Aún no estaba funcionando, pero al menos tengo un mensaje de error útil:

error C2440: 'specialization' : cannot convert from 'overloaded-function' to 'std::_Tree_const_iterator<_Mytree> (__thiscall std::set<_Kty>::*)(void) const'

Esto me deja saber que &U::end no es suficiente para VS a ser capaz de decir qué final() quiero. Un static_cast fija que:

typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type; 

poner todo de nuevo juntos y ejecutar su programa de pruebas en él ... éxito con VS2010. Es posible que encuentre que static_cast es en realidad todo lo que necesita, pero se lo dejé saber.

Supongo que la verdadera pregunta ahora es, ¿qué compilador tiene razón? Mi apuesta es la que fue consistente: g ++.

Editar: Jeesh ...

template <typename T> 
struct is_container 
{ 
    template <typename U, typename it_t = typename U::const_iterator > 
    struct sfinae 
    { 
     //typedef typename U::const_iterator it_t; 
     template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const > 
     struct type_ {}; 

     typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type; 
    }; 

    template <typename U> static char test(typename sfinae<U>::type*); 
    template <typename U> static long test(...); 

    enum { value = (1 == sizeof test<T>(0)) }; 
}; 



#include <iostream> 
#include <vector> 
#include <list> 
#include <set> 
#include <map> 

int main() 
{ 
    std::cout << is_container<std::vector<std::string> >::value << ' '; 
    std::cout << is_container<std::list<std::string> >::value << ' '; 
    std::cout << is_container<std::set<std::string> >::value << ' '; 
    std::cout << is_container<std::map<std::string, std::string> >::value << ' '; 
    std::cout << is_container<bool>::value << '\n'; 
} 
+1

@sbi @John @Rup Lamentablemente, este código no funciona. Si el tipo no tiene los métodos 'begin' o' end', no se compilará. – fredoverflow

+0

@Fred: Pero su código original también verificó 'begin()'/'end()', ¿no es así? (¿Y qué clase tendría un tipo anidado 'const_iterator', pero no' begin() '/' end() 'devolviéndolo?) – sbi

+3

@sbi: SFINAE significa" error de sustitución ** no ** es un error ". Si 'T' no tiene los métodos' begin' y 'end', entonces' is_container :: value' aún debe ** compilar ** (y dar falso). Y eso es exactamente lo que hace mi código correctamente en g ++. – fredoverflow

5

¿Por qué vas a hacer todo ese esfuerzo? Si desea verificar si existe U::begin(), ¿por qué no intentarlo?

template <typename T> 
struct is_container 
{ 
    template <typename U> static char test(U* u, 
     typename U::const_iterator b = ((U*)0)->begin(), 
     typename U::const_iterator e = ((U*)0)->end()); 
    template <typename U> static long test(...); 

    enum { value = (1 == sizeof test<T>(0)) }; 
}; 

Además de la comprobación de la existencia de U::begin() y U::end(), esto también comprueba si vuelven algo que se puede convertir en un const_iterator. También evita la trampa destacada por Stephan T. Lavavej mediante el uso de una expresión de llamada que debe ser respaldada, en lugar de asumir una firma en particular.

[edit] Lo sentimos, esto se basó en la instanciación de plantillas de VC10. Mejor enfoque (pone la comprobación de existencia de los tipos de argumentos, que hacen participan en la sobrecarga):

template <typename T> struct is_container 
{ 
    // Is. 
    template <typename U> 
    static char test(U* u, 
        int (*b)[sizeof(typename U::const_iterator()==((U*)0)->begin())] = 0, 
        int (*e)[sizeof(typename U::const_iterator()==((U*)0)->end())] = 0); 
    // Is not. 
    template <typename U> static long test(...); 

    enum { value = (1 == sizeof test<T>(0)) }; 
}; 
+0

Probado en VS2005, funciona bien incluso para 'is_container > :: value' (es' true') – MSalters

+0

'typename U :: const_iterator b = ((U *) 0) -> begin()' Cuando llenamos el argumento predeterminado, no llama al '' comenzar', ¿puede explicar los conceptos por los cuales 'begin' (' ((U *) 0) -> begin() 'no recibe el llamado? , Muchas gracias :) –

+0

@ Mr.Anubis: un argumento predeterminado se evalúa en el punto donde se llama a la función. Como la 'prueba ()' no se llama realmente ('sizeof (expr)' no evalúa la expresión), los argumentos predeterminados tampoco se evalúan. Sin embargo, 'sizeof (prueba (0))' necesita hacer una resolución de sobrecarga para determinar el tipo de retorno. – MSalters

1

Esto probablemente debería ser un comentario, pero no tienen suficientes puntos

@MSalters

Aunque su is_container funciona (casi) y yo mismo he usado su código, he descubierto dos problemas en él.

Primero es que el tipo deque<T>::iterator se detecta como un contenedor (en gcc-4.7). Parece que deque<T>::iterator tiene begin/end miembros y const_iterator tipo definido.

El segundo problema es que este código no es válido según los desarrolladores de GCC. I qoute: los valores de los argumentos predeterminados no son parte del tipo de función y no participan en la deducción. Ver GCC bug 51989

Actualmente estoy usando this (C++ sólo el 11) para is_container<T>:

template <typename T> 
struct is_container { 
    template < 
     typename U, 
     typename S = decltype (((U*)0)->size()), 
     typename I = typename U::const_iterator 
    > 
    static char test(U* u); 
    template <typename U> static long test(...); 
    enum { value = sizeof test<T>(0) == 1 }; 
}; 
+0

¿Entonces 'deque' no es un contenedor? – fredoverflow

+0

'deque' es un contenedor, pero' deque :: iterator' no lo es. –

+0

El segundo problema es real. Pasé por alto 14.7.1/2; los argumentos predeterminados no se instancian con la plantilla ellos mismos. – MSalters

3

con C++ 11, ahora hay mejores maneras de detectar esto. En lugar de confiar en la firma de funciones, simplemente los llamamos en un contexto SFINAE expresión:

#include <type_traits> // declval 

template<class T> 
class is_container{ 
    typedef char (&two)[2]; 

    template<class U> // non-const 
    static auto test(typename U::iterator*, int) 
     -> decltype(std::declval<U>().begin(), char()); 

    template<class U> // const 
    static auto test(typename U::const_iterator*, long) 
     -> decltype(std::declval<U const>().begin(), char()); 

    template<class> 
    static two test(...); 

public: 
    static bool const value = sizeof(test<T>(0, 0)) == 1; 
}; 

Live example on Ideone. Los parámetros int y long son sólo para eliminar la ambigüedad de resolución de sobrecarga cuando el contenedor ofrece tanto (o si iterator es typedef const_iterator iterator, como std::set está permitido) - literal 0 es de tipo int y fuerza a elegir la primera sobrecarga.

Cuestiones relacionadas