С++ 11 make_pair с указанными параметрами шаблона не компилируется

Я просто играл с g++ 4.7 (одним из более поздних снимков) с включенным -std = С++ 11. Я попытался скомпилировать часть моей существующей кодовой базы, и один случай, который потерпел неудачу, несколько смутил меня.

Буду признателен, если кто-нибудь сможет объяснить, что происходит.

Вот код:

#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;
}

Я понимаю, что make_pair предназначен для использования в качестве случая (1) (если я укажу типы, то я мог бы также использовать (3)), но я не понимаю, почему он не работает в этом случае.

Точная ошибка:

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

Опять же, вопрос здесь просто "что происходит?" Я знаю, что могу исправить проблему, удалив спецификацию шаблона, но я просто хочу знать, что здесь не так под обложками.

  • g++ 4.4 компилирует этот код без проблем.
  • Удаление -std = С++ 11 также компилируется с кодом без проблем.

Ответ 1

Это не значит, что std::make_pair предназначен для использования; вы не должны явно указывать аргументы шаблона.

С++ 11 std::make_pair принимает два аргумента типа T&& и U&&, где T и U являются параметрами типа шаблона. Фактически, он выглядит так (игнорируя тип возврата):

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

Когда вы вызываете std::make_pair и явно указываете аргументы типа шаблона, дедукция аргументов не выполняется. Вместо этого аргументы типа заменяются непосредственно в объявлении шаблона, давая:

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

Обратите внимание, что оба этих типа параметров являются ссылками rvalue. Таким образом, они могут связываться только с rvalues. Это не проблема для второго аргумента, который вы передаете, 7, потому что это выражение rvalue. s, однако, является выражением lvalue (оно не является временным и не перемещается). Это означает, что шаблон функции не соответствует вашим аргументам, поэтому вы получаете ошибку.

Итак, почему это работает, когда вы явно не указываете, что T и U находятся в списке аргументов шаблона? Короче говоря, ссылочные параметры rvalue являются особыми в шаблонах. Отчасти благодаря языковой функции, называемой ссылочным коллапсированием, ссылочный параметр rvalue типа A&&, где A является параметром типа шаблона, может связываться с любым типом A.

Не имеет значения, является ли A lvalue, rvalue, const-квалифицированным, нестабильным или неквалифицированным, A&& может привязываться к этому объекту (опять же, если и только если A сам является параметром шаблона).

В вашем примере мы вызываем вызов:

make_pair(s, 7)

Здесь s является lvalue типа std::string и 7 является значением r типа int. Поскольку вы не указываете аргументы шаблона для шаблона функции, выполняется вывод аргумента шаблона, чтобы выяснить, что такое аргументы.

Чтобы связать s, lvalue, с T&&, компилятор выводит T как std::string&, давая аргумент типа std::string& &&. Однако ссылок на ссылки нет, поэтому эта "двойная ссылка" рушится, чтобы стать std::string&. s является совпадением.

Простое связывание 7 с U&&: компилятор может вывести U как int, указав параметр типа int&&, который успешно связывается с 7, потому что это значение r.

Есть много тонкостей с этими новыми языковыми функциями, но если вы следуете одному простому правилу, это довольно легко:

Если аргумент шаблона можно вывести из аргументов функции, пусть это будет выведено. Не указывайте явно аргумент, если вы абсолютно не обязаны.

Пусть компилятор выполнит тяжелую работу, и в 99,9% случаев это будет именно то, что вы хотели в любом случае. Когда это не то, что вы хотели, вы обычно получаете ошибку компиляции, которую легко идентифицировать и исправить.