C++ 11 make_pair con parámetros de plantilla especificados no compila
templates g++ (1)
Estaba jugando con g ++ 4.7 (una de las instantáneas posteriores) con -std = c ++ 11 activado. Traté de compilar parte de mi código base existente y un caso que falló algo me confunde.
Agradecería si alguien puede explicar lo que está pasando.
Aquí está el código
#include <utility>
#include <iostream>
#include <vector>
#include <string>
int main ( )
{
std::string s = "abc";
// 1 ok
std::pair < std::string, int > a = std::make_pair ( s, 7 );
// 2 error on the next line
std::pair < std::string, int > b = std::make_pair < std::string, int > ( s, 7 );
// 3 ok
std::pair < std::string, int > d = std::pair < std::string, int > ( s, 7 );
return 0;
}
Entiendo que make_pair está destinado a ser utilizado como el (1) caso (si especifico los tipos, entonces también podría usar (3)), pero no entiendo por qué está fallando en este caso.
El error exacto es:
test.cpp: En la función ''int main ()'': test.cpp: 11: 83: error: no hay función de coincidencia para llamar a ''make_pair (std :: string &, int)'' test.cpp: 11: 83: nota: candidate is: en el archivo incluido en /gcc4.7/usr/local/lib/gcc/i686-pc-linux-gnu/4.7.0/../../../../include/c++/4.7. 0 / utilidad: 72: 0, de test.cpp: 1: /gcc4.7/usr/local/lib/gcc/i686-pc-linux-gnu/4.7.0/../../../. ./include/c++/4.7.0/bits/stl_pair.h:274:5: note constexpr std :: pair :: __ type, typename std :: __ decay_and_strip <_T2> :: __ type> std :: make_pair (_T1 && , _T2 &&) /gcc4.7/usr/local/lib/gcc/i686-pc-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/ stl_pair.h: 274: 5: nota: argumento de la plantilla deducción / sustitución fallida: test.cpp: 11: 83: nota: no se puede convertir ''s'' (tipo ''std :: string {aka std :: basic_string}'') para escribir ''std :: basic_string &&''
Nuevamente, la pregunta aquí es solo "¿qué está pasando?" Sé que puedo solucionar el problema eliminando la especificación de la plantilla, pero solo quiero saber qué está fallando aquí bajo las sábanas. Gracias por adelantado.
EDITAR:
- g ++ 4.4 compila este código sin problemas.
- Removing -std = c ++ 11 también compila con código sin problemas.
Esta no es la forma en que std::make_pair
está destinado a ser utilizado; no se supone que especifiques explícitamente los argumentos de la plantilla.
El C ++ 11 std::make_pair
toma dos argumentos, de tipo T&&
y U&&
, donde T
y U
son parámetros de tipo de plantilla. Efectivamente, se ve así (ignorando el tipo de devolución):
template <typename T, typename U>
[return type] make_pair(T&& argT, U&& argU);
Cuando llama a std::make_pair
y especifica explícitamente los argumentos de tipo de plantilla, no se produce ninguna deducción de argumento. En su lugar, los argumentos de tipo se sustituyen directamente en la declaración de la plantilla, produciendo:
[return type] make_pair(std::string&& argT, int&& argU);
Tenga en cuenta que ambos tipos de parámetros son referencias rvalue. Por lo tanto, solo pueden unirse a valores r. Esto no es un problema para el segundo argumento que aprueba, 7
, porque esa es una expresión rvalue. s
, sin embargo, es una expresión lvalue (no es temporal y no se mueve). Esto significa que la plantilla de función no coincide con sus argumentos, por lo que obtiene el error.
Entonces, ¿por qué funciona cuando no se especifica explícitamente qué T
y U
están en la lista de argumentos de la plantilla? En resumen, los parámetros de referencia rvalue son especiales en las plantillas. Debido en parte a una característica de lenguaje llamada colapso de referencia , un parámetro de referencia rvalue de tipo A&&
, donde A
es un parámetro de tipo de plantilla, puede vincularse a cualquier clase de A
No importa si A
es un lvalor, un valor r, const-qualified, volátil-calificado o no calificado, un A&&
puede enlazar a ese objeto (de nuevo, si y solo si A
es en sí mismo un parámetro de plantilla).
En su ejemplo, hacemos la llamada:
make_pair(s, 7)
Aquí, s
es un lvalue de tipo std::string
y 7
es un valor de r de tipo int
. Como no especifica los argumentos de plantilla para la plantilla de función, la deducción de argumento de plantilla se realiza para descubrir cuáles son los argumentos.
Para vincular s
, un lvalue, a T&&
, el compilador deduce T
para ser std::string&
, produciendo un argumento de tipo std::string& &&
. Sin embargo, no hay referencias a referencias, por lo que esta "referencia doble" colapsa para convertirse en std::string&
. s
es un partido.
Es simple vincular 7
a U&&
: el compilador puede deducir que U
es int
, produciendo un parámetro de tipo int&&
, que se une con éxito a 7
porque es un valor r.
Hay muchas sutilezas con estas nuevas características del lenguaje, pero si sigues una regla simple, es bastante fácil:
Si se puede deducir un argumento de plantilla a partir de los argumentos de la función, déjalo deducirse. No proporcione explícitamente el argumento a menos que sea absolutamente necesario.
Deje que el compilador haga el trabajo duro, y el 99.9% del tiempo será exactamente lo que quería de todos modos. Cuando no es lo que quería, generalmente obtendrá un error de compilación que es fácil de identificar y corregir.