C ++ 11 make_pair con parámetros de plantilla especificados no se compila

84

Solo estaba jugando con g ++ 4.7 (una de las instantáneas posteriores) con -std = c ++ 11 habilitado. Traté de compilar algo de mi base de código existente y un caso que falló me confunde un poco.

Agradecería que alguien me pudiera 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 usarse como el caso (1) (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: In function ‘int main()’:
    test.cpp:11:83: error: no matching function for call to ‘make_pair(std::string&, int)’
    test.cpp:11:83: note: candidate is:
    In file included from /gcc4.7/usr/local/lib/gcc/i686-pc-linux-gnu/4.7.0/../../../../include/c++/4.7.0/utility:72:0,
                 from 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: template<class _T1, class _T2> constexpr std::pair<typename std::__decay_and_strip<_T1>::__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:
note:   template argument deduction/substitution failed:
    test.cpp:11:83: note:   cannot convert ‘s’ (type ‘std::string {aka std::basic_string<char>}’) to type ‘std::basic_string<char>&&’

Una vez más, la pregunta aquí es simplemente "¿qué está pasando?" Sé que puedo solucionar el problema eliminando la especificación de la plantilla, pero solo quiero saber qué está fallando aquí debajo de las cubiertas.

  • g ++ 4.4 compila este código sin problemas.
  • La eliminación de -std = c ++ 11 también se compila con código sin problemas.
vmpstr
fuente
6
Excelente pregunta. Otro ejemplo más de un cambio rotundo sutil en C ++ 11, similar al cambio rotundo en la std::vectorconstrucción . Al menos este produce un error de compilación y no un cambio silencioso en la semántica.
James McNellis
1
Si tengo una variable entera i. Quiero hacer un par con yo y otro objeto. ¿Cómo debo llamar exactamente a makepair. 1) make_pair <* i, obj> 2) int && j = i; make_pair <j, obj>? Ambos no funcionan. ¿Cuál es la forma correcta de hacerlo?
PHcoDer

Respuestas:

135

Esta no es la forma en std::make_pairque debe usarse; no se supone que debe especificar explícitamente los argumentos de la plantilla.

C ++ 11 std::make_pairtoma dos argumentos, de tipo T&&y U&&, donde Ty Uson parámetros de tipo de plantilla. Efectivamente, se ve así (ignorando el tipo de retorno):

template <typename T, typename U>
[return type] make_pair(T&& argT, U&& argU);

Cuando llama std::make_pairy especifica explícitamente los argumentos del tipo de plantilla, no se realiza ninguna deducción de argumentos. En cambio, los argumentos de tipo se sustituyen directamente en la declaración de plantilla, lo que produce:

[return type] make_pair(std::string&& argT, int&& argU);

Tenga en cuenta que ambos tipos de parámetros son referencias de rvalue. Por lo tanto, solo se pueden vincular a rvalues. Esto no es un problema para el segundo argumento que pasa 7, porque es una expresión rvalue. s, sin embargo, es una expresión lvalue (no es temporal y no se está moviendo). Esto significa que la plantilla de la función no coincide con sus argumentos, razón por la cual obtiene el error.

Entonces, ¿por qué funciona cuando no especifica explícitamente qué Ty qué Uestán en la lista de argumentos de la plantilla? En resumen, los parámetros de referencia de rvalue son especiales en las plantillas. Debido en parte a una característica del lenguaje llamada colapso de referencias , un parámetro de referencia rvalue de tipo A&&, donde Aes un parámetro de tipo de plantilla, se puede vincular a cualquier tipo de A.

No importa si Aes un lvalue, un rvalue, const-calificado, volatile-calificado o no calificado, un A&&puede vincularse a ese objeto (nuevamente, si y solo si Aes en sí mismo un parámetro de plantilla).

En su ejemplo, hacemos la llamada:

make_pair(s, 7)

Aquí, ses un valor l de tipo std::stringy 7es un valor r de tipo int. Dado que no especifica los argumentos de la plantilla para la plantilla de la función, la deducción de los argumentos de la plantilla se realiza para averiguar cuáles son los argumentos.

Para enlazar s, un lvalue, to T&&, el compilador deduce Tque es std::string&, produciendo un argumento de tipo std::string& &&. Sin embargo, no hay referencias a referencias, por lo que esta "doble referencia" se colapsa para convertirse en std::string&. ses un partido.

Es sencillo al unen 7a U&&: el compilador puede deducir Uque int, produciendo un parámetro de tipo int&&, que se une al éxito 7porque es un valor de lado derecho.

Hay muchas sutilezas con estas nuevas funciones de lenguaje, pero si sigue una regla simple, es bastante fácil:

Si se puede deducir un argumento de plantilla a partir de los argumentos de la función, que se deduzca. No proporcione explícitamente el argumento a menos que sea absolutamente necesario.

Deje que el compilador haga el trabajo duro, y el 99,9% de las veces será exactamente lo que quería de todos modos. Cuando no es lo que quería, normalmente obtendrá un error de compilación que es fácil de identificar y corregir.

James McNellis
fuente
6
Esta es una explicación muy buena y completa. ¡Gracias!
vmpstr
1
@James - ¿Es esa "una regla simple" de otro artículo o respuesta que debería leer?
Michael Burr
4
@MichaelBurr: No, me lo acabo de inventar. :-) ¡Espero que sea cierto! Creo que es verdad ... esa regla funciona para mí casi todo el tiempo.
James McNellis
1
@James: gracias. El 'recuadro de citas' que lo rodea me hizo pensar que podría haber sido algo escrito originalmente en otro lugar. Esta respuesta fue realmente informativa, y solo quería asegurarme de que no me estaba perdiendo algo en otro lugar.
Michael Burr
2
¿Esto también se aplica a las tuplas?
Ferruccio