2012-09-11 15 views
19

Utilicé el código de "Is there a way to test whether a C++ class has a default constructor (other than compiler-provided type traits)?".¿Por qué el uso de dos sizeofs funciona para verificar si una clase es construible por defecto, pero una no?

he modificado un poco para trabajar con todos mis casos de prueba:

template< class T > 
class is_default_constructible { 
    typedef int yes; 
    typedef char no; 


    // the second version does not work 
#if 1 
    template<int x, int y> class is_equal {}; 
    template<int x> class is_equal<x,x> { typedef void type; }; 

    template< class U > 
    static yes sfinae(typename is_equal< sizeof U(), sizeof U() >::type *); 
#else 
    template<int x> class is_okay { typedef void type; }; 

    template< class U > 
    static yes sfinae(typename is_okay< sizeof U() >::type *); 
#endif 

    template< class U > 
    static no sfinae(...); 

public: 
    enum { value = sizeof(sfinae<T>(0)) == sizeof(yes) }; 
}; 

¿Por qué funciona correctamente con la versión argumento de plantilla de la dos, pero no con la normal (establecido #if 0)? ¿Es esto un error del compilador? Estoy usando Visual Studio 2010.

he utilizado las siguientes pruebas:

BOOST_STATIC_ASSERT(is_default_constructible<int>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<bool>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<std::string>::value); 
BOOST_STATIC_ASSERT(!is_default_constructible<int[100]>::value); 

BOOST_STATIC_ASSERT(is_default_constructible<const std::string>::value); 

struct NotDefaultConstructible { 
    const int x; 
    NotDefaultConstructible(int a) : x(a) {} 
}; 

BOOST_STATIC_ASSERT(!is_default_constructible<NotDefaultConstructible>::value); 

struct DefaultConstructible { 
    const int x; 

    DefaultConstructible() : x(0) {} 
}; 

BOOST_STATIC_ASSERT(is_default_constructible<DefaultConstructible>::value); 

estoy realmente en una pérdida aquí:

  1. dos pruebas no están cumpliendo con la otra versión: int[100] y NotDefaultConstructible. Todas las pruebas tienen éxito con la versión del argumento de dos plantillas.
  2. Visual Studio 2010 no es compatible con std::is_default_constructible. Sin embargo, mi pregunta es acerca de por qué hay alguna diferencia en las dos implementaciones y por qué una funciona y la otra no.
+3

Por qué no comprobar si ya lo tienes en la biblioteca estándar como [ 'std :: is_default_constructible'] (http://en.cppreference.com/w/cpp/types/is_default_constructible)? –

+2

¿Qué no funciona? Funciona bien en g ++, a excepción de 'BOOST_STATIC_ASSERT (! Is_default_constructible :: value);' –

+0

@ BЈовић Si una afirmación está fallando, y esa afirmación es correcta, no funciona bien, ¿no? – hvd

Respuesta

3

(Mi respuesta es informado en gran medida por la respuesta anterior de DS.)

En primer lugar, el aviso de que tiene class is_okay { typedef void type; }, es decir, type es un miembro privado de is_okay. Esto significa que no es realmente visible fuera de la clase y, por lo tanto,

template< class U > 
static yes sfinae(typename is_equal< sizeof U(), sizeof U() >::type *); 

nunca tendrá éxito. Sin embargo, SFINAE originalmente no se aplicaba a esta situación en C++ 98; no fue hasta la resolución del DR 1170 que "la verificación de acceso [comenzó siendo] realizada como parte del proceso de sustitución". [1]

(Sorprendentemente, Paolo Carlini escribió que la entrada de blog hace tan sólo 10 días, por lo que su tiempo con esta pregunta es impecable. En casos como este, de acuerdo con Carlini, GCC antes del 4.8 no accedió a la verificación durante SFINAE en absoluto. Eso explica por qué no vio a GCC quejarse sobre la privacidad de type. Usted tendría que estar usando GCC tope de árbol de, literalmente, hace menos de dos semanas, con el fin de ver el comportamiento correcto.)

Clang (top-of-árbol) sigue la RD en -std=c++11 modo, pero da el error esperado en su modo predeterminado C++ 03 (es decir, Clang no sigue el DR en el modo C++ 03). Esto es un poco extraño, pero tal vez lo hacen por compatibilidad con versiones anteriores.

Pero de todos modos, en realidad no quiere que type sea privado en primer lugar. Lo que quería decir es struct is_equal y struct is_okay.

Con este cambio, Clang pasa todos sus casos de prueba. GCC 4.6.1 también pasa todos sus casos de prueba, excepto para int[100]. GCC piensa que int[100] está bien, mientras que usted está afirmando que es no bien.

Pero otro problema con su código es que no prueba lo que cree que está probando. estándar El C++, cláusula 8.5 # 10, dice muy claramente: [2]

Un objeto cuya inicializador es un conjunto vacío de paréntesis, es decir, (), será de valor inicializado.

Así que cuando se escribe sizeof U(), usted no está poniendo a prueba si U puede ser predeterminado -initialized; está probando si puede ser valor -inicializado!

... ¿O sí? Al menos en algunos de mis casos de prueba, los mensajes de error de GCC indicaban que U() se interpretaba como el nombre de un tipo - "función que devuelve U" - y que era el que int[100] tenía un comportamiento diferente. No veo cómo ese comportamiento es válido, pero realmente no entiendo las sutilezas sintácticas aquí.

Si usted realmente quiere decir poner a prueba predeterminado de inicialización, usted debe usar algo como sizeof *new U todas partes actualmente tiene sizeof U().

Por cierto, int[100] es default-initializable, y punto. El estándar es claro en lo que significa inicializar por defecto un tipo de matriz.

Por último, me pregunto si una de las causas del comportamiento excéntrico en su código es que usted está tratando de superar un sin adornos 0 (que es de tipo int) a una función cuyo conjunto de sobrecargas incluye una función de tomar void * y uno toma .... Pude entender totalmente si un compilador escogió el incorrecto en ese caso. Lo mejor sería intentar pasar 0 a una función tomando int.

Poniendo todo junto, aquí hay una versión de tu código que funciona perfectamente para mí (es decir, sin fallas de afirmación) tanto en ToT Clang como en GCC 4.6.1.

template< class T > 
class is_default_constructible { 
    typedef int yes; 
    typedef char no; 

    template<int x> struct is_okay { typedef int type; }; 

    template< class U > 
    static yes sfinae(typename is_okay< sizeof (*new U) >::type); 

    template< class U > 
    static no sfinae(...); 

public: 
    enum { value = sizeof(sfinae<T>(0)) == sizeof(yes) }; 
}; 

#if __has_feature(cxx_static_assert) 
#define BOOST_STATIC_ASSERT(x) static_assert(x, "or fail") 
#else 
#define dummy2(line) dummy ## line 
#define dummy(line) dummy2(line) 
#define BOOST_STATIC_ASSERT(x) int dummy(__COUNTER__)[(x) - 1] 
#endif 

#include <string> 

BOOST_STATIC_ASSERT(!is_default_constructible<int()>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<bool>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<std::string>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<int[100]>::value); 

BOOST_STATIC_ASSERT(is_default_constructible<const std::string>::value); 

struct NotDefaultConstructible { 
    const int x; 
    NotDefaultConstructible(int a) : x(a) {} 
}; 

BOOST_STATIC_ASSERT(!is_default_constructible<NotDefaultConstructible>::value); 

struct DefaultConstructible { 
    const int x; 

    DefaultConstructible() : x(0) {} 
}; 

BOOST_STATIC_ASSERT(is_default_constructible<DefaultConstructible>::value); 
+0

Has explicado esto muy bien. Apunté que falla 'int [100]', porque escribí una función de plantilla con un parámetro opcional 'T defaultValue = T()', que no funcionó para 'T = int [100]' en VS2010. Hasta ahora no sabía acerca de la inicialización por defecto vs. valor. – BlackHC

3

Esto parece casi con certeza un artefacto (error) del compilador, ya que g ++ se comporta (y falla) de manera diferente. Sólo puedo imaginar por qué VS comporta de manera diferente, pero una conjetura que parece razonable es que esta clase:

template<int x> class is_okay { typedef void type; }; 

tiene la misma definición sin tener en cuenta el parámetro de plantilla, así que quizás el compilador se salta un paso al analizar static sfinae(typename is_okay< sizeof U() >::type *); y considera Está bien definido sin mirar de cerca el parámetro de is_okay. Entonces, piensa que todo es predecible y construible.

Por qué ni VS ni g ++ son molestados por is_okay::type siendo privados, no sé. Parece que ambos deberían estarlo.

g ++, por otro lado, trata ambas versiones como equivalentes. En ambos, sin embargo, da un error diferente para int[100]. Ese es discutible si debe ser predecible o no. Parece que piensas que no debería ser. g ++ 47's std::is_default_constructible cree que es! Para obtener ese comportamiento (que probablemente sea más estándar), puede reemplazar T con typename boost::remove_all_extents<T>::type en la línea enum.

Cuestiones relacionadas