c++ templates template-specialization partial-specialization

C++ función plantilla especialización parcial?



templates template-specialization (6)

Sé que el siguiente código es una especialización parcial de una clase:

template <typename T1, typename T2> class MyClass { … }; // partial specialization: both template parameters have same type template <typename T> class MyClass<T,T> { … };

También sé que C ++ no permite la especialización parcial de la plantilla de función (solo está permitido). Pero, ¿significa mi código que he especializado parcialmente mi plantilla de función para argumentos del mismo tipo? Porque funciona para Microsoft Visual Studio 2010 Express! Si no, ¿podría explicar el concepto de especialización parcial?

#include <iostream> using std::cin; using std::cout; using std::endl; template <typename T1, typename T2> inline T1 max (T1 const& a, T2 const& b) { return a < b ? b : a; } template <typename T> inline T const& max (T const& a, T const& b) { return 10; } int main () { cout << max(4,4.2) << endl;; cout << max(5,5) << endl; int z; cin>>z; }


¿Qué es especialización?

Si realmente quieres entender las plantillas, debes echar un vistazo a los lenguajes funcionales. El mundo de las plantillas en C ++ es una sublengua puramente funcional propia.

En los lenguajes funcionales, las selecciones se realizan mediante la coincidencia de patrones :

-- An instance of Maybe is either nothing (None) or something (Just a) -- where a is any type data Maybe a = None | Just a -- declare function isJust, which takes a Maybe -- and checks whether it''s None or Just isJust :: Maybe a -> Bool -- definition: two cases (_ is a wildcard) isJust None = False isJust Just _ = True

Como puede ver, sobrecargamos la definición de isJust .

Bueno, las plantillas de clase C ++ funcionan exactamente de la misma manera. Proporciona una declaración principal que indica el número y la naturaleza de los parámetros. Puede ser solo una declaración, o también actúa como una definición (su elección), y luego puede (si así lo desea) proporcionar especializaciones del patrón y asociarlas a una versión diferente (de lo contrario sería tonto) de la clase .

Para las funciones de plantilla, la especialización es algo más incómoda: entra en conflicto con la resolución de sobrecarga. Como tal, se ha decidido que una especialización se relacionaría con una versión no especializada, y no se considerarían las especializaciones durante la resolución de sobrecarga. Por lo tanto, el algoritmo para seleccionar la función correcta se convierte en:

  1. Realice la resolución de sobrecarga, entre las funciones regulares y plantillas no especializadas
  2. Si se selecciona una plantilla no especializada, compruebe si existe una especialización que sería una mejor coincidencia

(para un tratamiento en profundidad, ver GotW # 49 )

Como tal, la especialización de plantillas de funciones es un ciudadano de la segunda zona (literalmente). En lo que a mí respecta, estaríamos mejor sin ellos: aún no me he encontrado con un caso en el que el uso de una especialización de plantilla no se pudiera resolver con la sobrecarga en su lugar.

¿Es esto una especialización de plantilla?

No, es simplemente una sobrecarga, y esto está bien. De hecho, las sobrecargas suelen funcionar como esperamos, mientras que las especializaciones pueden ser sorprendentes (recuerde el artículo de GotW que he vinculado).


Como la especialización parcial no está permitida, como apuntan otras respuestas, podría std::is_same utilizando std::is_same y std::enable_if , como se muestra a continuación:

template <typename T, class F> inline typename std::enable_if<std::is_same<T, int>::value, void>::type typed_foo(const F& f) { std::cout << ">>> messing with ints! " << f << std::endl; } template <typename T, class F> inline typename std::enable_if<std::is_same<T, float>::value, void>::type typed_foo(const F& f) { std::cout << ">>> messing with floats! " << f << std::endl; } int main(int argc, char *argv[]) { typed_foo<int>("works"); typed_foo<float>(2); }

Salida:

$ ./a.out >>> messing with ints! works >>> messing with floats! 2

Editar : en caso de que necesite tratar todos los demás casos restantes, puede agregar una definición que indique que los casos tratados no deberían coincidir ; de lo contrario, caería en definiciones ambiguas. La definición podría ser:

template <typename T, class F> inline typename std::enable_if<(not std::is_same<T, int>::value) and (not std::is_same<T, float>::value), void>::type typed_foo(const F& f) { std::cout << ">>> messing with unknown stuff! " << f << std::endl; } int main(int argc, char *argv[]) { typed_foo<int>("works"); typed_foo<float>(2); typed_foo<std::string>("either"); }

Que produce:

$ ./a.out >>> messing with ints! works >>> messing with floats! 2 >>> messing with unknown stuff! either

Aunque esta cuestión de todos los casos parece un poco aburrida, ya que tienes que decirle al compilador todo lo que ya has hecho, es bastante factible tratar hasta 5 o más especializaciones.


En el ejemplo, en realidad está sobrecargando (no especializando) la función max<T1,T2> . La sintaxis de especialización parcial debería haber parecido más o menos a continuación ( si se hubiera permitido ):

//Partial specialization is not allowed by the spec, though! template <typename T> inline T const& max<T,T> (T const& a, T const& b) { ^^^^^ <--- specializing here return 10; }

[Nota: en el caso de una plantilla de función, el estándar de C ++ solo permite la especialización completa (excluidas las extensiones del compilador).]


La especialización parcial no clasificada y no variable no está permitida, pero como se dijo:

Todos los problemas en la informática se pueden resolver con otro nivel de indirección. - David Wheeler

Agregar una clase para reenviar la llamada a la función puede resolver esto, aquí hay un ejemplo:

template <class Tag, class R, class... Ts> struct enable_fun_partial_spec; struct fun_tag {}; template <class R, class... Ts> constexpr R fun(Ts&&... ts) { return enable_fun_partial_spec<fun_tag, R, Ts...>::call( std::forward<Ts>(ts)...); } template <class R, class... Ts> struct enable_fun_partial_spec<fun_tag, R, Ts...> { constexpr static R call(Ts&&... ts) { return {0}; } }; template <class R, class T> struct enable_fun_partial_spec<fun_tag, R, T, T> { constexpr static R call(T, T) { return {1}; } }; template <class R> struct enable_fun_partial_spec<fun_tag, R, int, int> { constexpr static R call(int, int) { return {2}; } }; template <class R> struct enable_fun_partial_spec<fun_tag, R, int, char> { constexpr static R call(int, char) { return {3}; } }; template <class R, class T2> struct enable_fun_partial_spec<fun_tag, R, char, T2> { constexpr static R call(char, T2) { return {4}; } }; static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, ""); static_assert(fun<int>(1, 1) == 2, ""); static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, ""); static_assert(fun<char>(1, 1) == 2, ""); static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, ""); static_assert(fun<long>(1L, 1L) == 1, ""); static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, ""); static_assert(fun<double>(1L, 1L) == 1, ""); static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, ""); static_assert(fun<int>(1u, 1) == 0, ""); static_assert(std::is_same_v<decltype(fun<char>(1, ''c'')), char>, ""); static_assert(fun<char>(1, ''c'') == 3, ""); static_assert(std::is_same_v<decltype(fun<unsigned>(''c'', 1)), unsigned>, ""); static_assert(fun<unsigned>(''c'', 1) == 4, ""); static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, ""); static_assert(fun<unsigned>(10.0, 1) == 0, ""); static_assert( std::is_same_v<decltype(fun<double>(1, 2, 3, ''a'', "bbb")), double>, ""); static_assert(fun<double>(1, 2, 3, ''a'', "bbb") == 0, ""); static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, ""); static_assert(fun<unsigned>() == 0, "");


No. Por ejemplo, legalmente puede especializar std::swap , pero no puede definir legalmente su propia sobrecarga. Eso significa que no puede hacer que std::swap funcione para su propia plantilla de clase personalizada.

La sobrecarga y la especialización parcial pueden tener el mismo efecto en algunos casos, pero lejos de todo.


Respuesta tardía, pero algunos lectores tardíos pueden encontrarla útil: a veces, una función auxiliar, diseñada de tal manera que puede ser especializada, también puede resolver el problema.

Entonces imaginemos, esto es lo que tratamos de resolver:

template <typename R, typename X, typename Y> void function(X x, Y y) { R* r = new R(x); f(r, y); // another template function? } // for some reason, we NEED the specialization: template <typename R, typename Y> void function<R, int, Y>(int x, Y y) { // unfortunately, Wrapper has no constructor accepting int: Wrapper* w = new Wrapper(); w->setValue(x); f(w, y); }

OK, especialización de función de plantilla parcial, no podemos hacer eso ... Así que "exportaremos" la parte necesaria para la especialización a una función auxiliar, especialícela y úsela:

template <typename R, typename T> R* create(T t) { return new R(t); } template <> Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal... { Wrapper* w = new Wrapper(); w->setValue(n); return w; } template <typename R, typename X, typename Y> void function(X x, Y y) { R* r = create<R>(x); f(r, y); // another template function? }

Esto puede ser interesante especialmente si las alternativas (sobrecargas normales en lugar de especializaciones, la workaround propuesta por Rubens, ... - no es que estas sean malas o la mía sea mejor, solo otra ) compartirían bastante código común.