2009-07-24 8 views
10

Dado el siguiente código, ¿por qué se selecciona la función foo(T*)?¿Cuáles son las reglas para elegir entre funciones de plantilla sobrecargadas?

Si quito ella (la foo(T*)) el código todavía compila y funciona correctamente, pero v4.4.0 G ++ (y probablemente otros compiladores así) generará dos foo() funciones: una para char [4] y otro para char [ 7].

#include <iostream> 
using namespace std; 

template< typename T > 
void foo(const T&) 
{ 
    cout << "foo(const T&)" << endl; 
} 

template< typename T > 
void foo(T*) 
{ 
    cout << "foo(T*)" << endl; 
} 

int main() 
{ 
    foo("bar"); 
    foo("foobar"); 
    return 0; 
} 
+1

Divertido, MSVC9 nombra la función "foo " en los símbolos de depuración, pero, aparentemente porque reconoce que la implementación es idéntica, usa la misma implementación para ambas llamadas. - Interesante pregunta, de todos modos. – peterchen

+0

AFAIK VC duplica instancias de plantilla que dan como resultado un código idéntico. – sbi

+1

GCC (4.2.4) hace foo y foo instanciaciones en cualquier cosa hasta -O3, donde simplemente las elides por completo para estas implementaciones y las pone en línea. –

Respuesta

6

Formalmente, cuando se comparan las secuencias de conversión, las transformaciones de lvalue se ignoran. Las conversiones se agrupan en varias categorías, como el ajuste de calificación (T* ->T const*), transformación lvalue (int[N] ->int*, void() ->void(*)()), y otros.

La única diferencia entre sus dos candidatos es una transformación lvalue. Los literales de cadena son matrices que se convierten en punteros. El primer candidato acepta la matriz por referencia y, por lo tanto, no necesitará una transformación lvalue. El segundo candidato requiere una transformación lvalue.

Por lo tanto, si hay dos candidatos que las especializaciones de plantilla de función son igualmente viables al observar solo las conversiones, la regla es que la más especializada se elige haciendo una ordenación parcial de las dos.

Vamos a comparar los dos mirando su firma de su lista de parámetros de función

void(T const&); 
void(T*); 

Si elegimos algún tipo único Q por primera lista de parámetros y comprobar si coincide con la segunda lista de parámetros, estamos igualando Q contra T*. Esto fallará, ya que Q no es un puntero. Por lo tanto, el segundo es al menos tan especializado como el primero.

Si hacemos el camino inverso, igualamos Q* contra T const&. La referencia se descarta y los calificadores a niveles se ignoran, y el T restante se convierte en Q*. Esta es una coincidencia exacta para el fin de la ordenación parcial, y por lo tanto la deducción de la lista de parámetros transformados de la segunda contra el primer candidato tiene éxito. Como la otra dirección (contra la segunda) no tuvo éxito, el segundo candidato es más especializado que el primero, y en consecuencia, la resolución de sobrecarga preferirá la segunda, si hubiera ambigüedad.

En 13.3.3.2/3:

secuencia de conversión estándar S1 es una secuencia de conversión mejor que la secuencia de conversión estándar S2 si [...]

  • S1 es una subsecuencia adecuada de S2 (comparación de las secuencias de conversión en la forma canónica definido por 13.3.3.1.1, con exclusión de cualquier transformación lValue; la secuencia de conversión de identidad se considera que es una subsecuencia de cualquier no secuencia -Identity conversión) o, si no que [...]

Entonces 13.3.3/1

  • deje que ICSi (F) denote la secuencia de conversión implícita que convierte el argumento i-th en la lista al tipo del parámetro i-ésimo de la función viable F. 13.3.3.1 define las secuencias de conversión implícitas y 13.3.3.2 define qué significa que una secuencia de conversión implícita es una mejor secuencia de conversión o una secuencia de conversión peor que otra.

Teniendo en cuenta estas definiciones, una función F1 viable se define para ser una función mejor que otro F2 función viable si para todos los argumentos i, ICSI (F1) no es una secuencia de conversión peor que la ICSI (F2), y luego [...]

  • F1 y F2 son especializaciones plantilla de función, y la plantilla de función para F1 es más especializado que el molde para la F2 de acuerdo con las reglas de ordenación parciales se describe en 14.5.5.2, o, si no que , [...]

Finalmente, aquí está la tabla de conversiones implícitas que pueden participar en una secuencia de conversión estándar en 13.3.3.1.1/3.

Conversion sequences http://img259.imageshack.us/img259/851/convs.png

+0

Este NO es un caso de preferencia de no plantilla, porque el compilador no elige ninguna función que no sea de plantilla. El hecho es que const char [] es una coincidencia mucho más cercana a T * que T & (porque const char [] y const char * son equivalentes). –

+0

lo siento, pasé por alto la plantilla <...> cláusula antes de la segunda. fijo :) –

+0

Sin embargo, su afirmación de que la coincidencia con 'T *' está más cerca es incorrecta :) –

-1

Causa "" es un char *, que se ajusta perfectamente a la función foo (T *). Cuando elimine esto, el compilador intentará hacerlo funcionar con foo (T &), lo que requiere que pase una referencia a la matriz de caracteres que contiene la cadena.

El compilador no puede generar una función que reciba una referencia a char, ya que está pasando una matriz completa, por lo que debe desreferenciarla.

+0

Malo también. Por favor no adivines '" "' es un 'char const [1]'. Usted tampoco puede 'pasar referencia'. Las expresiones siempre tienen el tipo de no referencia. –

+0

Nunca dije que no sea (en realidad, es "", es const [2]), lo que quise decir es que la función T * es la preferida, si existe. En cuanto a la declaración 'passing ref', intentaba decir que la opción menos preferida es T &, que es básicamente una referencia a T (en este caso, referencia a char [N]), aunque mi respuesta no sonaba del todo correcto, tu correcto con eso. Corrígeme si me equivoco (y después de que lo hagas, simplemente eliminaré mi publicación, ya que no es útil en comparación con la tuya, que es mucho más completa). –

0

Sobre la base de reglas de resolución de sobrecarga (Apéndice B de C++ Templates: The Complete Guide tiene una buena visión general), literales de cadena (const char []) están más cerca de T * a T &, porque el compilador no hace distinción entre Char [] y char *, por lo que T * es la coincidencia más cercana (const T * sería una coincidencia exacta).

De hecho, si se pudiera añadir:

template<typename T> 
void foo(const T[] a) 

(que puede no), el compilador le dirá que esta función es una redefinición de:

template<typename T> 
void foo(const T* a) 
+0

'T *' de ninguna manera es una coincidencia más cercana. Pruebe 'void f (char const (&)[1]); void f (char const *); int main() {f (" ");}': esta llamada, que se asemeja a la situación anterior usando no-templates, es ambigua. –

+0

@ litb: T * es una coincidencia mucho más cercana que T & en este caso Sí, si incluye un TAMAÑO específico (y [N]), entonces eso lo hace ambiguo, pero T * coincide con una matriz arbitraria mucho más de cerca que T & does. –

+0

@Nick, mientras que void foo (T x []) es un sinónimo de void foo (T * x), no significa que no puedas instanciar void foo (T &) siendo T un tipo de matriz.El equivalente es void foo (ArrayBaseType (&) [ArraySize]) – AProgrammer

2

La respuesta completa es bastante técnico.

En primer lugar, los literales de cadena tienen char const[N] tipo.

Luego hay una conversión implícita de char const[N] a char const*.

Por lo tanto, ambas funciones de su plantilla coinciden, una usando el enlace de referencia, una usando la conversión implícita. Cuando están solos, ambas funciones de la plantilla son capaces de manejar las llamadas, pero cuando ambas están presentes, tenemos que explicar por qué el segundo foo (instanciado con T = const de char [N]) es una mejor coincidencia que el primero (instanciado con T = char).Si nos fijamos en las reglas de sobrecarga (según lo dado por litb), la elección entre

void foo(char const (&x)[4)); 

y

void foo(char const* x); 

es ambigua (las reglas son bastante complicado pero se puede comprobar mediante la escritura de funciones no plantilla con tales firmas y ver que el compilador se queja). En ese caso, la elección se hace al segundo porque ese es más especializado (de nuevo las reglas para este pedido parcial son complicadas, pero en este caso es porque puede pasar un char const[N] a un char const* pero no un char const* a un char const[N] de la misma manera que void bar(char const*) es más especializado que void bar(char*) porque puede pasar un char* a un char const* pero no viceversa).

Cuestiones relacionadas