C ++ 11 make_pair com parâmetros de template especificados não compila

84

Eu estava apenas brincando com o g ++ 4.7 (um dos instantâneos posteriores) com -std = c ++ 11 habilitado. Tentei compilar parte da minha base de código existente e um caso que falhou me confunde um pouco.

Eu apreciaria se alguém pudesse explicar o que está acontecendo.

Aqui está o 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;
}

Eu entendo que make_pair deve ser usado como o caso (1) (se eu especificar os tipos, então eu também posso usar (3)), mas não entendo por que ele está falhando neste caso.

O erro exato é:

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>&&’

Novamente, a questão aqui é apenas "o que está acontecendo?" Sei que posso corrigir o problema removendo a especificação do modelo, mas só quero saber o que está falhando aqui nos bastidores.

  • g ++ 4.4 compila este código sem problemas.
  • A remoção de -std = c ++ 11 também compila com o código sem problemas.
vmpstr
fonte
6
Excelente pergunta. Mais um exemplo de uma alteração sutil no C ++ 11, semelhante à alteração significativa na std::vectorconstrução . Pelo menos este produz um erro do compilador e não uma mudança silenciosa na semântica.
James McNellis
1
Se eu tiver uma variável inteira i. Eu quero fazer par com i e outro objeto. Como exatamente devo chamar de makepair. 1) make_pair <* i, obj> 2) int && j = i; make_pair <j, obj>? Ambos não estão funcionando. Qual é a maneira correta de fazer isso?
PHcoDer

Respostas:

135

Não é assim que std::make_pairse destina a ser usado; você não deve especificar explicitamente os argumentos do modelo.

O C ++ 11 std::make_pairleva dois argumentos, do tipo T&&e U&&, onde Te Usão parâmetros do tipo de modelo. Efetivamente, fica assim (ignorando o tipo de retorno):

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

Quando você chama std::make_paire especifica explicitamente os argumentos do tipo de modelo, nenhuma dedução de argumento ocorre. Em vez disso, os argumentos de tipo são substituídos diretamente na declaração do modelo, resultando em:

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

Observe que ambos os tipos de parâmetro são referências rvalue. Portanto, eles só podem ser vinculados a rvalues. Isso não é um problema para o segundo argumento que você passa 7,, porque essa é uma expressão rvalue. s, no entanto, é uma expressão lvalue (não é temporária e não está sendo movida). Isso significa que o modelo de função não é compatível com seus argumentos, e é por isso que você obtém o erro.

Então, por que funciona quando você não especifica explicitamente o que Te o que Uestão na lista de argumentos do modelo? Resumindo, os parâmetros de referência rvalue são especiais nos modelos. Devido em parte a um recurso de linguagem denominado colapso de referência , um parâmetro de referência rvalue do tipo A&&, onde Aé um parâmetro de tipo de modelo, pode ser vinculado a qualquer tipo de A.

Não importa se Aé um lvalue, um rvalue, const-qualificado, volatile-qualificado ou não-qualificado, A&&pode vincular-se a esse objeto (novamente, se e somente se Afor um parâmetro de modelo).

No seu exemplo, fazemos a chamada:

make_pair(s, 7)

Aqui, sé um lvalue do tipo std::stringe 7é um rvalue do tipo int. Visto que você não especifica os argumentos do template para o template da função, a dedução do argumento do template é realizada para descobrir quais são os argumentos.

To bind s, an lvalue, to T&&, o compilador deduz Tser std::string&, produzindo um argumento do tipo std::string& &&. Não há referências a referências, entretanto, esta "referência dupla" se reduz para se tornar std::string&. sé uma correspondência.

É simples para ligar 7para U&&: o compilador pode deduzir U-se int, produzindo um parâmetro do tipo int&&, que se liga com sucesso 7, porque é um rvalue.

Existem muitas sutilezas com esses novos recursos de linguagem, mas se você seguir uma regra simples, é muito fácil:

Se um argumento de modelo pode ser deduzido dos argumentos da função, deixe-o ser deduzido. Não forneça explicitamente o argumento, a menos que seja absolutamente necessário.

Deixe o compilador fazer o trabalho pesado e, 99,9% das vezes, será exatamente o que você queria de qualquer maneira. Quando não é o que você queria, você geralmente obtém um erro de compilação que é fácil de identificar e corrigir.

James McNellis
fonte
6
Esta é uma explicação muito boa e abrangente. Obrigado!
vmpstr de
1
@James - é "uma regra simples" de outro artigo ou resposta que devo ler?
Michael Burr
4
@MichaelBurr: Nah, acabei de inventar isso. :-) Então, espero que seja verdade! Eu acho que é verdade ... essa regra funciona para mim praticamente o tempo todo.
James McNellis
1
@James: obrigado. A 'caixa de citação' ao redor me fez pensar que poderia ter sido algo originalmente escrito em outro lugar. Essa resposta foi muito informativa, e eu só queria ter certeza de que não estava perdendo nada em outro lugar.
Michael Burr
2
Isso também se aplica a tuplas?
Ferruccio