2012-10-12 152 views
26

supongamos que tengo estas declaracionesusando SFINAE para la plantilla de la especialización de clase

template<typename T> class User; 
template<typename T> class Data; 

y quiero aplicar para User<>T = Data<some_type>y cualquier clase derivada de Data<some_type> sino también tener en cuenta otras especializaciones definidos en otros lugares.

Si yo no tuviera ya la declaración de la plantilla de clase User<>, pude simplemente

template<typename T, 
     typename A= typename std::enable_if<is_Data<T>::value>::type> 
class User { /*...*/ }; 

donde

template<template<typename> data>> struct is_Data 
{ static const bool value = /* some magic here (not the question) */; }; 

Sin embargo, esto tiene dos parámetros de plantilla y por lo tanto los enfrentamientos con la anterior declaración, donde User<> se declara con solo un parámetro de plantilla. ¿Hay algo mas que pueda hacer?

(Nota

template<typename T, 
     typename A= typename std::enable_if<is_Data<T>::value>::type> 
class User<T> { /*...*/ }; 

no funciona (argumentos de plantilla por defecto no pueden ser utilizados en especializaciones parciales), ni tampoco

template<typename T> class User<Data<T>> { /*...*/ }; 

ya que no permite tipos derivados desde Data<>, tampoco lo hace

template<typename T> 
class User<typename std::enable_if<is_Data<T>::value,T>::type> 
{ /*...*/ }; 

desde parámetro plantilla T no se utiliza en la especialización parcial.)

+2

SFINAE ** ** puede se aplicará para seleccionar las especializaciones de plantillas, ver http://en.cppreference.com/w/cpp/types/enable_if – Walter

+0

¡Así que puede! Aprendí algo –

+0

No creo entender por qué la versión 'static_assert' no funcionaría. ¿Cuidado para elaborar? – jrok

Respuesta

6

Como dijo que aún esperaba una respuesta mejor, esta es mi opinión. No es perfecto, pero creo que te lleva lo más lejos posible usando SFINAE y especializaciones parciales. (Supongo Conceptos proporcionarán una solución completa y elegante, pero vamos a tener que esperar un poco más de tiempo para eso.)

La solución se basa en una característica de plantillas de alias que se especificó sólo recientemente, en los borradores de trabajo estándar después de la versión final de C++ 14, pero ha sido compatible con implementaciones durante un tiempo. La redacción relevante del borrador N4527 [14.5.7p3] es:

Sin embargo, si el ID de la plantilla es dependiente, la subsiguiente sustitución del argumento de la plantilla aún se aplica a la plantilla-id. [Ejemplo:

template<typename...> using void_t = void; 
template<typename T> void_t<typename T::foo> f(); 
f<int>(); // error, int does not have a nested type foo 

-fin ejemplo]

Aquí está un ejemplo completo la implementación de esta idea:

#include <iostream> 
#include <type_traits> 
#include <utility> 

template<typename> struct User { static void f() { std::cout << "primary\n"; } }; 

template<typename> struct Data { }; 
template<typename T, typename U> struct Derived1 : Data<T*> { }; 
template<typename> struct Derived2 : Data<double> { }; 
struct DD : Data<int> { }; 

template<typename T> void take_data(Data<T>&&); 

template<typename T, typename = decltype(take_data(std::declval<T>()))> 
using enable_if_data = T; 

template<template<typename...> class TT, typename... Ts> 
struct User<enable_if_data<TT<Ts...>>> 
{ 
    static void f() { std::cout << "partial specialization for Data\n"; } 
}; 

template<typename> struct Other { }; 
template<typename T> struct User<Other<T>> 
{ 
    static void f() { std::cout << "partial specialization for Other\n"; } 
}; 

int main() 
{ 
    User<int>::f(); 
    User<Data<int>>::f(); 
    User<Derived1<int, long>>::f(); 
    User<Derived2<char>>::f(); 
    User<DD>::f(); 
    User<Other<int>>::f(); 
} 

Correr imprime:

primary 
partial specialization for Data 
partial specialization for Data 
partial specialization for Data 
primary 
partial specialization for Other 

Como se puede ver , hay una arruga: la especialización parcial no se selecciona para DD, y no puede ser, debido a la forma en que lo declaramos. Entonces, ¿por qué no nos limitamos a decir

template<typename T> struct User<enable_if_data<T>> 

y que se pueda emparejar DD así? En realidad, esto funciona en GCC, pero es rechazado correctamente por Clang y MSVC debido [14.5.5p8.3, 8.4] ([P8.3] pueden desaparecer en el futuro, ya que es redundante - CWG 2033):

  • La lista de argumentos de la especialización no debe ser idéntica a la lista de argumentos implícitos de la plantilla primaria.
  • La especialización debe ser más especializada que la plantilla primaria (14.5.5.2).

User<enable_if_data<T>> es equivalente a User<T> (sustitución de módulo en ese argumento por defecto, que se maneja por separado, como se explica por la primera cita anterior), por lo tanto una forma inválida de especialización parcial.Desafortunadamente, hacer coincidir cosas como DD requeriría, en general, un argumento de especialización parcial del formulario T - no hay otra forma que pueda tener y aún coincida con cada caso. Entonces, me temo que podemos decir concluyentemente que esta parte no se puede resolver dentro de las limitaciones dadas. (Hay Core issue 1980, que hace alusión a algunas de las posibles futuras normas relativas al uso de alias plantilla, pero dudo que va a hacer nuestro caso válido.)

Mientras las clases derivadas de Data<T> son en sí mismas especializaciones de la plantilla, lo que limita aún más las usar la técnica anterior funcionará, así que espero que te sirva de algo.


apoyo Compiler (esto es lo que probé, otras versiones pueden funcionar tan bien):

  • Clang 3.3 - 3.6.0, con -Wall -Wextra -std=c++11 -pedantic - funciona como se describe anteriormente.
  • GCC 4.7.3 - 4.9.2, mismas opciones - igual que el anterior. Curiosamente, GCC 5.1.0 - 5.2.0 ya no selecciona la especialización parcial con la versión correcta del código. Esto parece una regresión. No tengo tiempo para armar un informe de errores adecuado; Siéntase libre de hacerlo si lo desea. El problema parece estar relacionado con el uso de paquetes de parámetros junto con un parámetro de plantilla de plantilla. De todos modos, GCC acepta la versión incorrecta usando enable_if_data<T>, por lo que puede ser una solución temporal.
  • MSVC: Visual C++ 2015, con /W4, funciona como se describe anteriormente. Las versiones más antiguas no les gusta el decltype en el argumento por defecto, pero la técnica en sí sigue funcionando - reemplazando el argumento por defecto con otra forma de expresar la restricción hace que funcione en 2013 Actualización 4.
18

SI la declaración original de User<> se puede adaptar a

template<typename, typename=std::true_type> class User; 

entonces podemos encontrar una solución (siguiente comentario de Luc Danton , en lugar de usar std::enable_if)

template<typename> 
struct is_Data : std::false_type {}; 
template<typename T> 
struct is_Data<Data<T>> : std::true_type {}; 

template<typename T> 
class User<T, typename is_Data<T>::type > 
{ /* ... */ }; 

Cómo Alguna vez, este no responde a la pregunta original, ya que se requiere para cambiar la definición original de User. Todavía estoy esperando una mejor respuesta. Esto podría ser una que de manera concluyente demuestra que no hay otra solución posible.

+0

La solución se muestra correctamente en el enlace publicado; sin embargo, no se transfirió/adaptó correctamente en esta respuesta; debería ser una especialización parcial como la siguiente: 'plantilla clase Usuario :: valor > :: tipo> {...}; '... publicará una" edición ". – etherice

+0

@etherice gracias. arreglado en edición. – Walter

+0

Creo que [esta respuesta] (http: // stackoverflow.com/a/31213703/1269661) podría adaptarse para hacer sfinae sin modificar la definición original – Predelnik

5

Como sólo desea ponerlo en práctica cuando una única condición es verdadera, la solución más fácil es utilizar una afirmación estática. No requiere SFINAE, da un error de compilación claro si utiliza incorrectamente y la declaración de User<> no necesita ser adaptado:

template<typename T> class User { 
    static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>"); 
    /** Implementation. **/ 
}; 

Consulte también: When to use static_assert instead of SFINAE?.El static_assert es un constructo C++ 11, sin embargo hay un montón soluciones disponibles para pre-C++ 11 compiladores, como:

#define STATIC_ASSERT(consdition,name) \ 
    typedef char[(condition)?1:-1] STATIC_ASSERT_ ## name 

Si la declaración de user<> se puede cambiar y que quieren dos implementaciones dependiendo del valor de is_Data, entonces también es una solución que no utiliza SFINAE:

template<typename T, bool D=is_Data<T>::value> class User; 

template<typename T> class User<T, true> { 
    static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>"); // Optional 
    /* Data implementation */ 
}; 

template<typename T> class User<T, false> { 
    static_assert(!is_Data<T>::value, "T is (a subclass of) Data<>"); // Optional 
    /* Non-data implementation */ 
}; 

las afirmaciones estáticas sólo comprueba si el usuario no especifica por error del parámetro de plantilla incorre D ctly. Si D no se especifica explícitamente, entonces las aserciones estáticas se pueden omitir.

+1

Esto realmente no resuelve el problema que tenía. Todavía quería permitir otras especializaciones de 'Datos ' (editará la pregunta para mencionar eso). – Walter

Cuestiones relacionadas