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

85

Я просто играл с 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 также без проблем компилируется с кодом.
vmpstr
источник
6
Отличный вопрос. Еще один пример тонкого критического изменения в C ++ 11, похожего на критическое изменение std::vectorконструкции . По крайней мере, это приводит к ошибке компилятора, а не к тихому изменению семантики.
Джеймс МакНеллис,
1
Если у меня есть целочисленная переменная i. Я хочу создать пару с i и другим объектом. Как именно мне называть makepair? 1) make_pair <* i, obj> 2) int && j = i; make_pair <j, obj>? Оба не работают. Как правильно это сделать?
PHcoDer

Ответы:

136

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

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

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

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

make_pair(s, 7)

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

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

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

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

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

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

Джеймс МакНеллис
источник
6
Это очень хорошее и исчерпывающее объяснение. Спасибо!
vmpstr
1
@James - это "одно простое правило" из другой статьи или ответа, который я должен прочитать?
Майкл Берр,
4
@MichaelBurr: Нет, это я только что придумал. :-) Так что, надеюсь, это правда! Я думаю, это правда ... это правило работает для меня почти все время.
Джеймс МакНеллис,
1
@ Джеймс: спасибо. «Цитата» вокруг него заставила меня подумать, что это могло быть что-то изначально написанное в другом месте. Этот ответ был действительно информативным, и я просто хотел убедиться, что не упускаю что-то еще.
Майкл Берр,
2
Применимо ли это и к кортежам?
Ферруччо