2009-10-02 14 views
20

lo tanto, sé que hay una diferencia entre estos dos fragmentos de código:Diferencias entre especialización de plantilla y sobrecarga para funciones?

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

template <> 
int inc(const int& t) 
{ 
    return t + 1; 
} 

y

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

int inc(const int& t) 
{ 
    return t + 1; 
} 

estoy confundido en cuanto a lo que las diferencias funcionales entre estos dos son. ¿Alguien puede mostrar algunas situaciones en las que estos snippits actúan de forma diferente el uno del otro?

+0

Volví a subir la pregunta porque esta es una de las áreas oscuras en C++ (al menos para mí)! – AraK

+5

¿Qué es lo que pides en particular? Solo algunas diferencias, o algunos casos donde el peligro está en su lugar? Por ejemplo, seguramente 'inc <> (10);' se comporta diferente para estos dos fragmentos, pero ¿es eso lo que quieres escuchar? –

Respuesta

14

Solo puedo pensar en algunas diferencias: he aquí algunos ejemplos que no necesariamente causan daño (creo). Estoy omitiendo las definiciones para mantenerlo terso

template <typename T> T inc(const T& t); 
namespace G { using ::inc; } 
template <> int inc(const int& t); 
namespace G { void f() { G::inc(10); } } // uses explicit specialization 

// --- against --- 

template <typename T> T inc(const T& t); 
namespace G { using ::inc; } 
int inc(const int& t); 
namespace G { void f() { G::inc(10); } } // uses template 

Eso se debe a que las especializaciones no se encuentran en la búsqueda de nombres, sino por argumento de comparación, por lo que una declaración using a considerar de forma automática una especialización introducido más tarde.

Entonces, por supuesto, no puede especializar parcialmente las plantillas de funciones.La sobrecarga sin embargo logra algo muy similar por orden parcial (utilizando diferentes tipos ahora, para hacer mi punto)

template <typename T> void f(T t); // called for non-pointers 
template <typename T> void f(T *t); // called for pointers. 

int a; 
void e() { 
    f(a); // calls the non-pointer version 
    f(&a); // calls the pointer version 
} 

que no sería posible con una plantilla de función especialización explícita. Otro ejemplo es cuando se trata de referencias, lo que provoca la deducción argumento de plantilla para buscar una coincidencia exacta de los tipos involucrados (base de módulo/relaciones entre las clases derivadas y constness):

template<typename T> void f(T const &); 
template<> void f(int * const &); 

template<typename T> void g(T const &); 
void g(int * const &); 

int a[5]; 
void e() { 
    // calls the primary template, not the explicit specialization 
    // because `T` is `int[5]`, not `int *` 
    f(a); 

    // calls the function, not the template, because the function is an 
    // exact match too (pointer conversion isn't costly enough), and it's 
    // preferred. 
    g(a); 
} 

recomiendo que utilice siempre sobrecarga, porque es más rica (permite algo así como la especialización parcial), y además puede colocar la función en el espacio de nombres que desee (aunque ya no esté estrictamente sobrecargado). Por ejemplo, en lugar de tener que especializarse en std::swap en el espacio de nombres std::, puede colocar su sobrecarga swap en su propio espacio de nombres y hacer que sea invocable por ADL.

Hagas lo que hagas, nunca mezcle la especialización y la sobrecarga, será un desastre como this article señala. El estándar tiene un párrafo preciosa al respecto

la colocación de las declaraciones especialización explícita de plantillas de funciones, plantillas de clase, funciones miembro de plantillas de clase, los miembros de datos estáticos de plantillas de clase, clases de miembros de plantillas de clase, plantillas de clase miembro de plantillas de clase, plantillas de función miembro de plantillas de clase, funciones miembro de plantillas miembro de plantillas de clase, funciones miembro de plantillas miembro de clases no plantilla, plantillas de función miembro de clases miembro de plantillas de clase, etc., y la colocación de declaraciones de especialización parcial de plantillas de clase, plantillas de clase de miembro de clases que no son de plantilla, plantillas de clase de miembro de plantillas de clase, etc., pueden afectar si un programa está bien formado de acuerdo con el posicionamiento relativo de la especialización explícita decl araciones y sus puntos de instanciación en la unidad de traducción como se especifica arriba y abajo. Al escribir una especialización, tenga cuidado con su ubicación; o para hacer que compile será una prueba que encienda su autoinmolación.

+0

Gran artículo. . – rlbond

+0

+1 agradable - Me gusta :) –

+0

gracias amigos :) –

5

La especialización de plantillas es más genérica que la sobrecarga. Puede especializar cosas como clases en lugar de simples funciones. La sobrecarga solo se aplica a las funciones.

ACTUALIZACIÓN: Para aclarar más por comentario de AraK, realmente está comparando manzanas y naranjas aquí. La sobrecarga de funciones se usa para introducir la capacidad de tener diferentes funciones para compartir un solo nombre, si tienen diferentes firmas. La especialización de plantillas se usa para definir un fragmento de código específico para un parámetro de tipo específico. No puede tener una especialización de plantilla si no tiene una plantilla. Si elimina la primera pieza de código que declara la plantilla genérica, recibirá un error de tiempo de compilación si intenta utilizar la especialización de plantilla.

Por lo tanto, el objetivo de la especialización de plantilla es bastante diferente de una sobrecarga de función. Simplemente se comportan de manera similar en su ejemplo, mientras que son fundamentalmente diferentes.

Si proporciona una sobrecarga, está declarando un método independiente que tiene el mismo nombre. No está impidiendo que la plantilla se use con el parámetro de tipo específico. Para demostrar este hecho, intentan:

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

int inc(const int& t) 
{ 
    return t + 42; 
} 

#include <iostream> 
int main() { 
    int x = 0; 
    x = inc<int>(x); 
    std::cout << "Template: " << x << std::endl; // prints 1. 

    x = 0; 
    x = inc(x); 
    std::cout << "Overload: " << x << std::endl; // prints 42. 
} 

Como se puede ver, en este ejemplo, hay dos distintas funciones para incint valores: inc(const int&) y inc<int>(const int&). No se pudo expandir la plantilla genérica usando int si utilizó la especialización de plantilla.

+2

Buena nota, aunque no responde la pregunta. – AraK

+1

AraK: La pregunta, como yo lo entiendo, es básicamente, por qué hay dos formas de lograr lo mismo. Demuestra un resultado similar logrado por dos métodos y pregunta por la diferencia entre los dos métodos. Mi respuesta es que estos dos métodos se aplican a diferentes contextos y un solo ejemplo donde los dos son aplicables no los hace equivalentes. –

+0

Dije que es una buena nota, pero no responde a la diferencia de funcionalidad entre * especialización de la plantilla de función * y * sobrecarga de función *. Estás hablando de especialización de plantillas en general. Creo que el título es engañoso un poco. – AraK

1

AFAIK no hay diferencia funcional. Todo lo que puedo agregar es que si tiene tanto una especialización de función de plantilla como una función ordinaria definida, entonces no hay ambigüedad de sobrecarga ya que se favorece la función ordinaria.

4

Un ejemplo de ello:

#include <cstdio> 

template <class T> 
void foo(T) 
{ 
    puts("T"); 
} 

//template <> 
void foo(int*) 
{ 
    puts("int*"); 
} 

template <class T> 
void foo(T*) 
{ 
    puts("T*"); 
} 

int main() 
{ 
    int* a; 
    foo(a); 
} 

Se llegó a sugerir que utilice sobrecargas en plantillas para las funciones y la especialización de salir de clases. Se discute con mayor detalle en Why Not Specialize Function Templates?

1

Solo para explicar el primer punto mencionado por litb en su answer. Las especializaciones solo se verifican una vez que la resolución de sobrecarga ha seleccionado una plantilla primaria. El resultado puede dar lugar a algunas sorpresas, donde una función está sobrecargado y tiene especializaciones explícitas:

template <typename T> void foo (T); // Primary #1 
template <> void foo<int*> (int*); // Specialization of #1 

template <typename T> void foo (T*); // Primary #2 

void bar (int * i) 
{ 
    foo(i); 
} 

Al elegir qué función debe llamar, los siguientes pasos se llevan a cabo:

  1. Nombre de búsqueda encuentra ambas plantillas primarias.
  2. Cada plantilla está especializada y la resolución de sobrecarga intenta seleccionar una mejor función en función de las conversiones entre los argumentos y los parámetros.
  3. En este caso, no hay diferencia en la calidad de las conversiones.
  4. Las reglas de pedido parciales se usan luego para seleccionar la plantilla más especializada. En este caso, ese es el segundo parimario "foo (T *)".

Sólo después de estos pasos, cuando la mejor función ha sido seleccionada serán considerados especializaciones explícito de la función seleccionada . (En este caso, el número 2 primario no tiene ninguno, por lo que no se consideran ninguno).

La única manera de llamar a la especialización explícita por encima de aquí, es el uso de realidad argumentos de plantilla explícitas en la llamada:

void bar (int * i) 
{ 
    foo<int*> (i); // expliit template argument forces use of primary #1 
} 

Una buena regla general es tratar de evitar que las sobrecargas que también son explicily especializada , ya que las reglas resultantes son bastante complejas.

Cuestiones relacionadas