C++11 make_pair with specified template parameters doesn’t compile

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.

Leave a Comment