c++ - ¿Cómo escribir la plantilla `is_complete`?
templates typetraits (7)
La respuesta dada por Alexey Malistov se puede usar en MSVC con una modificación menor:
namespace
{
template<class T, int discriminator>
struct is_complete {
static T & getT();
static char (& pass(T))[2];
static char pass(...);
static const bool value = sizeof(pass(getT()))==2;
};
}
#define IS_COMPLETE(X) is_complete<X,__COUNTER__>::value
Desafortunadamente, la macro predefinida de CONTADOR no es parte del estándar, por lo que no funcionaría en todos los compiladores.
Después de responder a this pregunta, intentaba encontrar la plantilla is_complete
en la biblioteca de Boost y me di cuenta de que no existe tal plantilla en Boost.TypeTraits. ¿Por qué no hay tal plantilla en la biblioteca de Boost? ¿Cómo debería ser?
//! Check whether type complete
template<typename T>
struct is_complete
{
static const bool value = ( sizeof(T) > 0 );
};
...
// so I could use it in such a way
BOOST_STATIC_ASSERT( boost::is_complete<T>::value );
El código anterior no es correcto, porque es ilegal aplicar sizeof
a un tipo incompleto. ¿Cuál será una buena solución? ¿Es posible aplicar SFINAE en este caso de alguna manera?
Bueno, este problema no se puede resolver en general sin violar la regla ODR , pero hay una solution específica para la plataforma que funciona para mí.
Me temo que no puede implementar un tipo de rasgos de tipo is_complete
. La implementación dada por @Alexey no se compila en G ++ 4.4.2 y G ++ 4.5.0:
error: inicializando el argumento 1 de ''static char (& is_complete :: pass (T)) [2] [with T = Foo]''
En mi Mac, con G ++ 4.0.1 evaluando is_complete<Foo>::value
where struct Foo;
es un rendimiento incompleto a true
que es aún peor que un error del compilador.
T
puede estar completo e incompleto en el mismo programa, dependiendo de la unidad de traducción, pero siempre es del mismo tipo. Como consecuencia, como se comentó anteriormente, is_complete<T>
es siempre el mismo tipo.
Por lo tanto, si respeta la ODR, no es posible que is_complete<T>
evalúe a diferentes valores dependiendo de dónde se use; de lo contrario, significaría que tiene diferentes definiciones para is_complete<T>
que ODR prohíbe.
EDITAR: Como la respuesta aceptada, yo mismo __COUNTER__
una solución que usa la macro __COUNTER__
para crear una instancia de un tipo diferente de is_complete<T, int>
cada vez que se IS_COMPLETE
macro IS_COMPLETE
. Sin embargo, con gcc, no pude hacer que SFINAE funcionara en primer lugar.
No puedo encontrar nada en el estándar que garantice que el tamaño de un tipo incompleto produzca 0. Sin embargo, se requiere que si T está incompleto en algún momento, pero se complete más adelante en esa unidad de traducción, que todas las referencias a T se refieran. para el mismo tipo, de modo que, mientras lo leo, incluso si T está incompleta donde se invocó su plantilla, sería necesario que indique que se completó si T se completa en algún lugar de esa unidad de traducción.
Puede que sea un poco tarde, pero hasta ahora, ninguna solución de C ++ 11 funcionó tanto para tipos completos como abstractos.
Así que, aquí estás.
Con VS2015 (v140), g ++> = 4.8.1, clang> = 3.4, esto está funcionando:
template <class T, class = void>
struct IsComplete : std::false_type
{};
template <class T>
struct IsComplete< T, decltype(void(sizeof(T))) > : std::true_type
{};
Gracias a Bat-Ulzii Luvsanbat: https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/
Con VS2013 (V120):
namespace Details
{
template <class T>
struct IsComplete
{
typedef char no;
struct yes { char dummy[2]; };
template <class U, class = decltype(sizeof(std::declval< U >())) >
static yes check(U*);
template <class U>
static no check(...);
static const bool value = sizeof(check< T >(nullptr)) == sizeof(yes);
};
} // namespace Details
template <class T>
struct IsComplete : std::integral_constant< bool, Details::IsComplete< T >::value >
{};
Este está inspirado en las redes internas y afirma que el nombre de la plantilla T no está completo.
Resolver esto requiere realizar el cálculo en el argumento predeterminado de la plantilla de rasgo, ya que intentar cambiar la definición de una plantilla viola la regla ODR (aunque una combinación de __COUNTER__
y namespace {}
puede funcionar alrededor de ODR).
Esto está escrito en C ++ 11, pero puede ajustarse para funcionar en el modo C ++ 03 de un compilador compatible con C ++ 11 moderadamente reciente.
template< typename t >
typename std::enable_if< sizeof (t), std::true_type >::type
is_complete_fn( t * );
std::false_type is_complete_fn( ... );
template< typename t, bool value = decltype( is_complete_fn( (t *) nullptr ) )::value >
struct is_complete : std::integral_constant< bool, value > {};
El argumento predeterminado se evalúa donde se nombra la plantilla, por lo que puede cambiar contextualmente entre diferentes definiciones. No hay necesidad de una especialización y definición diferente en cada uso; solo necesitas uno para true
y otro para false
.
La regla se da en §8.3.6 / 9, que se aplica igualmente a los argumentos predeterminados de la función y a los argumentos predeterminados de la plantilla:
Los argumentos predeterminados se evalúan cada vez que se llama a la función.
Pero cuidado, usar esto dentro de una plantilla es casi seguro que violará la ODR. Una plantilla instanciada en un tipo incompleto no debe hacer nada diferente de si se crea una instancia en un tipo completo. Personalmente solo quiero esto para static_assert
.
Por cierto, este principio también puede ser útil si desea ir por el otro camino e implement la funcionalidad de __COUNTER__
usando plantillas y sobrecargas.
Simplemente insinuando para indicar que una respuesta (no dada por mí) a una pregunta no relacionada brinda una solución para la is_complete<T>
.
La respuesta está here . No lo estoy pegando a continuación para no obtener crédito por ello por error.
template<class T>
struct is_complete {
static T & getT();
static char (& pass(T))[2];
static char pass(...);
static const bool value = sizeof(pass(getT()))==2;
};