2011-01-02 15 views
6

Considere la situación en la que una plantilla de función necesita reenviar un argumento manteniendo su valor en caso de que sea un valor no const, pero es agnóstico de lo que realmente es el argumento, como en :Deducción de tipo de argumento, referencias y valores

template <typename T> 
void target(T&) { 
    cout << "non-const lvalue"; 
} 

template <typename T> 
void target(const T&) { 
    cout << "const lvalue or rvalue"; 
} 


template <typename T> 
void forward(T& x) { 
    target(x); 
} 

Cuando x es un valor de lado derecho, en lugar de T deduciéndose a un tipo constante, se da un error:

int x = 0; 
const int y = 0; 

forward(x); // T = int 
forward(y); // T = const int 
forward(0); // Hopefully, T = const int, but actually an error 
forward<const int>(0); // Works, T = const int 

parece que para forward para manejar rvalues ​​(sin callin g para argumentos de plantilla explícitos) debe haber una sobrecarga forward(const T&), aunque su cuerpo sería un duplicado exacto.

¿Hay alguna forma de evitar esta duplicación?

+0

Si está sobrecargando lvaluenes como este, merece que le disparen. – Yttrill

Respuesta

5

Este es un problema conocido y el propósito de las referencias de rvalue en C++ 0x. El problema no tiene solución genérica en C++ 03.

Hay alguna razón histórica arcaica por la que esto ocurre que es muy inútil. Recuerdo haber preguntado una vez y la respuesta me deprimió mucho.

+0

Soy una de las personas responsables de esta regla de sonido. La deducción de variables de tipo solo puede calcular tipos de valores. "const T" no es un tipo de valor. La duplicación que esto requiere no es inútil sino semánticamente esencial. En caso de que no lo sepas, un valor r nunca tiene tipo "const", en realidad son bastante mutables. Dag Brueck de Suecia es responsable de eso, es así que puedes llamar g (f (x) .modify()) que se necesita para establecer correctamente algunos valores. – Yttrill

+0

@Ytt: ¿Qué quiere decir con exactamente "tipo de valor" aquí? – fredoverflow

+1

@Yttrill: Correcto. Por lo tanto, los valores r son perfectamente modificables, pero no pueden vincularse a referencias mutables. Entonces, si tienes 'g (f (x) .modify())' está perfectamente bien, pero nunca podrías tener 'g (f (x))' donde g las llamadas se modifican. No estoy seguro de que estés presentando un argumento en contra de mi sugerencia de que este estado de cosas era ridículo. – Puppy

2

When x is an rvalue

Pero x nunca es un valor de lado derecho, porque los nombres son lvalues.

Is there any way to avoid this duplication?

Hay una forma en C++ 0x:

#include <utility> 

template <typename T> 
void forward(T&& x) // note the two ampersands 
{ 
    target(std::forward<T>(x)); 
} 

Gracias al hacer referencia a las reglas el colapso, la expresión std::forward<T>(x) es de la misma categoría de valor como argumento a su propia función de avance.

0

Suponiendo que hay k argumentos, como yo lo entiendo, la única "solución" en C++ 03 es escribir manualmente funciones de reenvío de 2^k tomando todas las combinaciones posibles de los parámetros & y const&. Por ejemplo, imagine que target() en realidad tomó 2 parámetros. A continuación, tendría:

template <typename T> 
void forward2(T& x, T& y) { 
    target(x, y); 
} 

template <typename T> 
void forward2(T& x, T const& y) { 
    target(x, y); 
} 

template <typename T> 
void forward2(T const& x, T& y) { 
    target(x, y); 
} 

template <typename T> 
void forward2(T const& x, T const& y) { 
    target(x, y); 
} 

Obviamente, esto se pone muy difícil de manejar para k grande, por lo tanto, las referencias rvalue en C++ 0x como han mencionado otras respuestas.

+0

Nunca debe hacerlo así que esto nunca es necesario. – Yttrill

+0

@Yttrill: ¿Eh? Sería muy útil tener la capacidad de reenviar una llamada de función arbitraria. Consulte la sección "Ejemplos de motivación" en la parte superior de http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm. –

+0

@j_random_hacker: Por supuesto, acepto que el reenvío es útil. Lo que no es útil es sobrecargar en tipos sin valor. NUNCA debes tener dos funciones del mismo nombre donde los argumentos difieran solo en "const" o "ref" porque la semántica es diferente, por lo que los nombres también deberían ser. – Yttrill

2

En general, este desagradable problema con las plantillas debe requerir duplicación porque la semántica donde una variable es const o no, o una referencia o no, son bastante distintas.

La solución C++ 11 para esto es "decltype" pero es una mala idea porque todo lo que hace es compuesto y ya está roto sistema de tipo.

No importa lo que diga la Norma o el Comité, "const int" no es y nunca será un tipo. Tampoco será "int &" alguna vez un tipo. Por lo tanto, nunca se debe permitir que un parámetro de tipo en una plantilla se vincule con tales no-tipos, y afortunadamente para la deducción este es el caso. Lamentablemente, todavía puedes forzar explícitamente esta sustitución sin principios.

Hay algunas reglas idiotas que tratan de "arreglar" este problema, tales como "const int const" reducir a "const int", ni siquiera estoy seguro de lo que sucede si usted consigue "Int & &": recordar incluso el estándar no cuenta "int &" como un tipo, hay un tipo "int valor-I", pero que es distinta:

int x;  // type is lvalue int 
int &y = x; // type is lvalue int 

la solución adecuada a este problema es muy simple: todo es un objeto mutable . Deseche "const" (no es tan útil) y elimine las referencias, lvalues ​​y rvalues. Está bastante claro que todos los tipos de clases son direccionables, rvalue o no (el puntero "this" es la dirección). Hubo un intento vano por parte del comité de prohibir asignar y abordar valores. El caso de direccionamiento funciona, pero se escapa fácilmente con un elenco trivial. El caso de asignación no funciona en absoluto (porque la asignación es una función miembro y los valores r no son const, siempre se puede asignar a un tipo de clase rvalue).

De todos modos la plantilla meta-programación multitud tiene "decltype" y con eso usted puede encontrar la codificación de una declaración incluyendo cualquier "const" y "&" bits y luego puede descomponer esa codificación utilizando varios operadores de biblioteca. Esto no pudo hacerse antes porque esa información no es realmente información de tipo ("ref" es en realidad información de asignación de almacenamiento).

+1

"y afortunadamente por deducción este es el caso ". En el ejemplo, 'forward (y)' deduce 'const int' para T. – uj2

+1

(1)" const int no es ... un tipo "? Claramente * es * un tipo en todas las versiones de C++, así que lo tomo, quiere decir que * no debería * ser un tipo, en algún mundo hipotético ideal - ¿no? (2) La deducción del argumento de la plantilla ciertamente puede inferir 'const int' o' int & ', no sé en qué estás hablando. (3) Cuando dice que la solución es "todo es un objeto mutable", ¿está proponiendo un cambio en el lenguaje C++? Solo puedo suponerlo, pero inicialmente pensé que estabas defendiendo el uso de solo un subconjunto de C++ que evita 'const' ... –

+0

... pero eso no funcionará para reenviar porque' T & 'y' T' son ahora tus solo posibles alternativas, y 'T &' no puede coincidir con un valor r, mientras que 'T' se deduce como un tipo que no es de referencia, por lo que no funcionará para las funciones que necesitan modificar el valor pasado. –

Cuestiones relacionadas