4

Both GCC and Clang refuse to compile this one:

#include <string>
#include <utility>

using namespace std;

int main() {
    const string s = "12345";
    const string& r = s;

    auto p = std::make_pair<string, string>(r, r);
}

GCC says:

error: cannot bind rvalue reference of type ‘std::__cxx11::basic_string<char>&&’ to lvalue of type ‘const std::string’ {aka ‘const std::__cxx11::basic_string<char>’}

While Clang says:

error: no matching function for call to 'make_pair'

Since I'm giving make_pair explicit types, why doesn't it construct new strings from const string&?


This one compiles:

auto p = std::make_pair<string, string>(string(r), string(r));
MWB
  • 11,740
  • 6
  • 46
  • 91

2 Answers2

7

Assuming C++11 or later:

std::make_pair is not supposed to be used with explicitly-specified template arguments. They are intended to be deduced from the function arguments via forwarding references. The signature of std::make_pair is

template<class T1, class T2>
constexpr std::pair<V1, V2> make_pair(T1&& t, T2&& u);

This shows that T1 and T2 are used as forwarding references and therefore shouldn't be specified explicitly. (V1/V2 are computed from T1/T2 by decay.)

Explicitly specifying the template arguments breaks the forwarding behavior. With string as template argument you get string&& in the function parameter, which is a rvalue reference that doesn't accept lvalues. You would need to provide const string& as template argument for T to make T&& also const string&.

But don't do that and just write

auto p = std::make_pair(r, r);

as is intended usage.


Before C++11 there were no forwarding references and std::make_pair looked like this:

template <class T1, class T2>
std::pair<T1, T2> make_pair(T1 t, T2 u);

So the code from your question would compile prior to C++11. Still, specifying the template arguments was redundant then as well.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • https://en.cppreference.com/w/cpp/utility/pair/make_pair doesn't seem to mention that explicit type arguments are bad here. Is this something one could have known (without SO) ? – MWB Mar 24 '23 at 00:20
  • 1
    @MWB If you have a forwarding reference in a function, then the author of the function wants to forward the function argument and this basically shows immediately that specifying the template argument is unintended. That's not specific to this function or the standard library. Specifying a template argument for a forwarding reference is kind of going against the purpose of having it in the first place. – user17732522 Mar 24 '23 at 00:24
2

Per pairs.spec the definition of make_pair looks like this:

template<class T1, class T2>
constexpr pair<unwrap_ref_decay_t<T1>, unwrap_ref_decay_t<T2>> make_pair(T1&& x, T2&& y);

and returns:

pair<unwrap_ref_decay_t<T1>, unwrap_ref_decay_t<T2>>(std::forward<T1>(x), std::forward<T2>(y))

unwrap_ref_decay_t is just std::decay_t here, since you are not using a reference_wrapper (and std::decay<string> is just string). Therefore when you explicitly name your template arguments, you end up with this signature:

constexpr pair<string, string> make_pair(string&& x, string&& y);

See how the function arguments went from a "forwarding reference" (T&&) to an r-value reference (string&&)?

Since you are providing const string& arguments, they cannot be converted, and therefore compilation fails. This is why when you explicitly pass string(r) it starts working.

The solution is to not pass the type arguments like user17732522 said

auto p = std::make_pair(r, r);
AndyG
  • 39,700
  • 8
  • 109
  • 143