make_pair with explicit types

Good day!
I have problem understanding why my example is not compiling when I explicitly specify the types for make_pair.

1
2
3
4
5
6
7
8
9
10
#include <string>

int main()
{
    std::string str;
    std::pair<std::string, size_t> data;
    data = std::make_pair<std::string, size_t>(str, 0); // error
    // data = std::make_pair(str, 0);                   // works fine
    // data = { str, 0 };                               // works fine
}


I tried different compilers on godbolt.org

In x86-64 GCC 13.2 I have this error:
 
cannot bind rvalue reference of type 'std::__cxx11::basic_string<char>&&' to lvalue of type 'std::string' {aka 'std::__cxx11::basic_string<char>'}


In x86-64 CLang 16.0.0 I have this error:
 
no matching function for call to 'make_pair'

If you look at the template functions std::make_pair doesn't allow for explicitly specifying the target type(s) of the parameters. They are deduced from the supplied parameter arguments.

https://en.cppreference.com/w/cpp/utility/pair/make_pair

That is why line 8 works, the derived types are std::string and int. The int is then promoted to size_t when the pair is created.

When working with std::pair you should also include the <utility> header, it's where std::pair and std::make_pair reside.

1
2
3
4
5
6
7
8
9
#include <string>
#include <utility>

int main( )
{
   std::string str;

   auto data = std::make_pair(str, 0);
}


auto makes creating a std::pair less cumbersome.

1
2
3
4
5
6
#include <utility>

int main( )
{
   auto data = std::make_pair("Hello World!", 0);
}


The std::pair types in this case are const char* and int.

If you want to create a pair with std::string and size_t you should use those types explicitly.

1
2
3
4
5
6
7
8
9
10
#include <string>
#include <utility>

int main( )
{
   std::string str;
   size_t      st;

   auto data = std::make_pair(str, st);
}
Satoshi Yoda, your code was valid in C++03. C++11 changed the definition of make_pair to enable perfect forwarding so now it doesn't always work if you specify the types explicitly but that's not the intended use case anyway.

The purpose of make_pair is to construct a pair by deducing the types from the arguments. This was necessary before C++17 because there were no class template argument deduction (CTAD) in the language.

If you want to specify the types you can just construct the pair directly.

 
data = std::pair<std::string, size_t>(str, 0);
Last edited on
> not compiling when I explicitly specify the types for make_pair.

If we must use explicit specification of the types involved,
we need to specify (correctly) the lvalue-ness or rvalue-ness of the types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <string>
#include <utility>

int main()
{
    std::string str;
    std::size_t i = 0 ;
    std::pair<std::string, size_t> data = std::make_pair<std::string&, size_t&>(str,i); // lvalue, lvalue
    data = std::make_pair<std::string&, size_t>(str,0); // lvalue, rvalue
    data = std::make_pair<std::string&, size_t&&>(str,0); // lvalue, rvalue
    data = std::make_pair<std::string, size_t&>( std::move(str), i ); // rvalue, lvalue
    data = std::make_pair<std::string&&, size_t&>( std::move(str), i ); // rvalue, lvalue
    data = std::make_pair<std::string, size_t>("abcd", 5 ); // rvalue, rvalue
    data = std::make_pair<std::string&&, size_t&&>("abcd", 5 ); // rvalue, rvalue
    data = std::make_pair<std::string, size_t>( std::move(str), std::move(i) ); // rvalue, rvalue
}

http://coliru.stacked-crooked.com/a/e2176bb3b5f9eca8
@JLBorges,

Wouldn't the reuse of a moved object after line 11, at lines 12 & 15, be considered UB? Or worse?

VS2022 whinges about it:
Warning	C26800 Use of a moved from object: ''str'' (lifetime.1).

I know this is just a simple bit of code to show how to do something, IMO it still isn't kind to show some potentially unsafe usage. YMMV.

I will admit I do need to delve deeper into the intricacies of value categories(lvalue, rvalue, glvalue, prvalue, xvalue), the terminology has seen some changes from standard to standard.
> Wouldn't the reuse of a moved object after line 11, at lines 12 & 15, be considered UB? Or worse?

For a scalar type, like std::size_t, a 'moved-from' object would be identical to the original.

For a standard library type, like std::string, a 'moved-from' object would be in a valid, though unspecified state.

So there is no UB in the example; even if, at least theoretically, the state of the objects would be unspecified.

A 'valid, but unpecified' state after being moved-from is a good target to aim for even for a non-library user-defined type. IMHO.
The VS2022 message is just a warning - not an error. The warning is correct and is just a prompt to the coder to make sure that the code as written is what is actually meant. It isn't an error or UB. But using an object after it has been set to a 'valid but unspecified state' could be the sign of a code error and so is flagged.
Thank you for answers!
It became a lot more clear now!

@Peter87,
About intended use case is the most useful one. I have not even considered using just std::pair<std::string, size_t> when I want to specify the types for some reason, thanks for pointing it out!

@JLBorges,
About lvalue-ness and rvalue-ness of the types: As I understood this should not be used extensively in practical code for the purpose of readability, but that example is very educating in general!
Topic archived. No new replies allowed.