Tienes que entender el problema de reenvío. Puede read the entire problem in detail, pero lo resumiré.
Básicamente, dada la expresión E(a, b, ... , c)
, queremos que la expresión f(a, b, ... , c)
sea equivalente. En C++ 03, esto es imposible. Hay muchos intentos, pero todos fallan en algún aspecto.
Lo más sencillo es utilizar un valor-i-referencia:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
Pero esto no puede manejar los valores temporales: f(1, 2, 3);
, como los que no se puede enlazar a un valor-i-referencia.
El siguiente intento podría ser:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
que fija el problema anterior, pero que pasa flops.Ahora deja de permitir E
que tienen argumentos no const:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
El tercer intento acepta const referencias, pero luego const_cast
's la const
distancia:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}
Este acepta todos los valores, puede transmitir todos los valores, pero potencialmente conduce a un comportamiento indefinido:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
Una solución final se encarga de todo correctamente ... a costa de estar imposible de mantener Usted proporciona sobrecargas de f
, con todos combinaciones de const y no const:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
N argumentos requieren 2 N combinaciones, una pesadilla. Nos gustaría hacer esto automáticamente
(Esto es efectivamente lo que obtenemos el compilador para hacer por nosotros en C++ 11.)
En C++ 11, tenemos la oportunidad de solucionar este problema. One solution modifies template deduction rules on existing types, but this potentially breaks a great deal of code. Así que tenemos que encontrar otra manera.
La solución es utilizar en su lugar las referencias de valor; podemos introducir nuevas reglas al deducir tipos de referencia rvalue y crear cualquier resultado deseado. Después de todo, no podemos romper el código ahora.
Si se les da una referencia a una referencia (referencia de la nota es un término que significa que abarca tanto T&
y T&&
), se utiliza la siguiente regla para averiguar el tipo resultante:
"[da] un tipo TR eso es una referencia a un tipo T, un intento de crear el tipo "referencia de lvalue a cv TR" crea el tipo "referencia de lvalue a T", mientras que un intento de crear el tipo "referencia de rvalue a cv TR" crea el tipo TR "
O en forma de tabla:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
A continuación, con deducción de argumento de plantilla: si un argumento es un valor-A, que les suministra el argumento de plantilla con una referencia a lvalue A. De lo contrario, se deduce normalmente . Esto proporciona las llamadas referencias universales (el término forwarding reference es ahora el oficial).
¿Por qué es esto útil? Debido a que combinamos, mantenemos la capacidad de realizar un seguimiento de la categoría de valor de un tipo: si se trata de un valor l, tenemos un parámetro de referencia lvalue; de lo contrario, tenemos un parámetro de referencia rvalue.
En código:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Lo último es a la categoría de valor de la variable "hacia adelante".Tenga en cuenta, una vez dentro de la función del parámetro se podría pasar como un valor izquierdo a cualquier cosa:
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
eso no es bueno. ¡E necesita obtener el mismo tipo de categoría de valor que tenemos! La solución es la siguiente:
static_cast<T&&>(x);
¿Qué hace esto? Considere que estamos dentro de la función deduce
, y hemos recibido un lvalue. Esto significa que T
es A&
, por lo que el tipo de objetivo para el molde estático es A& &&
, o simplemente A&
. Dado que x
ya es un A&
, no hacemos nada y nos queda una referencia lvalue.
Cuando se nos ha pasado un valor r, T
es A
, por lo que el tipo de destino para el modelo estático es A&&
. El elenco da como resultado una expresión rvalue, , que ya no se puede pasar a una referencia de lvalue. Hemos mantenido la categoría de valor del parámetro.
Poner esto junto nos da "reenvío perfecto":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Cuando f
recibe un valor izquierdo, E
obtiene un valor izquierdo. Cuando f
recibe un valor r, E
obtiene un valor r. Perfecto.
Y por supuesto, queremos deshacernos de lo feo. static_cast<T&&>
es críptico y extraño de recordar; En lugar de eso crea una función de utilidad llamada forward
, que hace lo mismo:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
¿No sería 'f' una función, y no una expresión? –
@sbi: Haha. :) <3 @mfukar: Es una expresión de llamada a función. 'f' en sí mismo podría ser cualquier cosa" invocable "; una función, puntero de función u objeto de función. – GManNickG
Gracias por su respuesta integral. Solo una pregunta, en deducir (1), x es tipo int && o int? Porque creo que leí en alguna parte que, si es de tipo rvalue, la T se resolvería en int. Entonces, con el extra &&, se convertiría en int && type. ¿Alguien podría aclarar eso? http://thbecker.net/articles/rvalue_references/section_08.html – Steveng