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.
SFINAE ** ** puede se aplicará para seleccionar las especializaciones de plantillas, ver http://en.cppreference.com/w/cpp/types/enable_if – Walter
¡Así que puede! Aprendí algo –
No creo entender por qué la versión 'static_assert' no funcionaría. ¿Cuidado para elaborar? – jrok