C ++ 11 make_pair с указанными параметрами шаблона не компилируется - PullRequest
75 голосов
/ 10 марта 2012

Я просто играл с g ++ 4.7 (одним из более поздних снимков) с включенной опцией -std = c ++ 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 = c ++ 11 также без проблем компилируется с кодом.

1 Ответ

120 голосов
/ 10 марта 2012

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

C ++ 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. Таким образом, они могут привязываться только к значениям. Это не проблема для второго передаваемого аргумента, 7, потому что это выражение rvalue. s, однако, является выражением lvalue (оно не является временным и не перемещается). Это означает, что шаблон функции не соответствует вашим аргументам, поэтому вы получаете ошибку.

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

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

В вашем примере мы делаем звонок:

make_pair(s, 7)

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

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

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

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

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

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

...