87

I was just playing around with g++ 4.7 (one of the later snapshots) with -std=c++11 enabled. I tried to compile some of my existing code base and one case that failed somewhat confuses me.

I would appreciate if someone can explain what is going on.

Here's the code:

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

I understand that make_pair is meant to be used as the (1) case (if I specify the types, then I might as well use (3)), but I don't understand why it's failing in this case.

The exact error is:

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

Again, the question here is just "what's going on?" I know that I can fix the problem by removing the template specification, but I just want to know what's failing here under the covers.

  • g++ 4.4 compiles this code with no problems.
  • Removing -std=c++11 also compiles with code with no problems.
1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
vmpstr
  • 5,051
  • 2
  • 25
  • 25
  • 7
    An excellent question. Yet another example of a subtle breaking change in C++11, similar to [the breaking change in `std::vector` construction](http://stackoverflow.com/questions/5759232/stdvector-default-construction-c11-and-breaking-changes). At least this one yields a compiler error and not a silent change in semantics. – James McNellis Mar 09 '12 at 23:53
  • 1
    If I have an integer variable i. I want to make pair with i and another object. How exactly should I call makepair. 1) make_pair<*i, obj> 2) int&& j = i; make_pair? Both are not working. Whats the correct way to do it? – PHcoDer Sep 07 '16 at 05:43

1 Answers1

143

This is not how std::make_pair is intended to be used; you are not supposed to explicitly specify the template arguments.

The C++11 std::make_pair takes two arguments, of type T&& and U&&, where T and U are template type parameters. Effectively, it looks like this (ignoring the return type):

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

When you call std::make_pair and explicitly specify the template type arguments, no argument deduction takes place. Instead, the type arguments are substituted directly into the template declaration, yielding:

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

Note that both of these parameter types are rvalue references. Thus, they can only bind to rvalues. This isn't a problem for the second argument that you pass, 7, because that is an rvalue expression. s, however, is an lvalue expression (it isn't a temporary and it isn't being moved). This means the function template is not a match for your arguments, which is why you get the error.

So, why does it work when you don't explicitly specify what T and U are in the template argument list? In short, rvalue reference parameters are special in templates. Due in part to a language feature called reference collapsing, an rvalue reference parameter of type A&&, where A is a template type parameter, can bind to any kind of A.

It doesn't matter whether the A is an lvalue, an rvalue, const-qualified, volatile-qualified, or unqualified, an A&& can bind to that object (again, if and only if A is itself a template parameter).

In your example, we make the call:

make_pair(s, 7)

Here, s is an lvalue of type std::string and 7 is an rvalue of type int. Since you do not specify the template arguments for the function template, template argument deduction is performed to figure out what the arguments are.

To bind s, an lvalue, to T&&, the compiler deduces T to be std::string&, yielding an argument of type std::string& &&. There are no references to references, though, so this "double reference" collapses to become std::string&. s is a match.

It's simple to bind 7 to U&&: the compiler can deduce U to be int, yielding a parameter of type int&&, which binds successfully to 7 because it is an rvalue.

There are lots of subtleties with these new language features, but if you follow one simple rule, it's pretty easy:

If a template argument can be deduced from the function arguments, let it be deduced. Don't explicitly provide the argument unless you absolutely must.

Let the compiler do the hard work, and 99.9% of the time it'll be exactly what you wanted anyway. When it isn't what you wanted, you'll usually get a compilation error which is easy to identify and fix.

ksyx
  • 41
  • 1
  • 9
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 1
    @James - is that "one simple rule" from another article or answer I should read? – Michael Burr Mar 10 '12 at 22:02
  • 4
    @MichaelBurr: Nah, I just made that up. :-) So, I hope it's true! I think it's true... that rule works for me pretty much all the time. – James McNellis Mar 10 '12 at 22:15
  • 1
    @James: thanks. The 'quote box' around it made me think it might have been something originally written elsewhere. This answer was really informative, and I just wanted to make sure I wasn't missing something somewhere else. – Michael Burr Mar 10 '12 at 22:30
  • 2
    Does this apply to tuples as well? – Ferruccio Jun 25 '15 at 15:43
  • OP already knows how make_pair is supposed to be used, he was just looking for more insight into the internals of the error. You don't need to scold him. – Michael Warner Nov 10 '15 at 02:09
  • @JamesMcNellis If I have an integer variable i. I want to make pair with i and another object. How exactly should I call makepair. 1) make_pair<*i, obj> 2) int&& j = i; make_pair? Both are not working. Whats the correct way to do it? – PHcoDer Sep 07 '16 at 05:43