What is the purpose of std::make_pair vs the constructor of std::pair?
Asked Answered
S

8

248

What is the purpose of std::make_pair?

Why not just do std::pair<int, char>(0, 'a')?

Is there any difference between the two methods?

Shinn answered 14/2, 2012 at 1:37 Comment(4)
In C++11, you can almost entirely do without make_pair. See my answer.Bearcat
In C++17, std::make_pair is redundant. There is an answer below that details this.Balkin
TL;DR: Just use curly braces. ;) { 0, 'a' } (Anyone who's ever coded JavaScript for some time will especially love this.)Simplex
std::make_pair(vec.cbegin(), vec.cend()) compared to std::pair<std::vector<std::string>::const_iterator, std::vector<std::string>::const_iterator>(vec.cbegin(), vec.cend())?Straightaway
P
213

(This answer is only correct for C++14 and earlier standards, due to CTAD)

The difference is that with std::pair you need to specify the types of both elements, whereas std::make_pair will create a pair with the type of the elements that are passed to it, without you needing to tell it. That's what I could gather from various docs anyways.

See this example from http://www.cplusplus.com/reference/std/utility/make_pair/

pair <int,int> one;
pair <int,int> two;

one = make_pair (10,20);
two = make_pair (10.5,'A'); // ok: implicit conversion from pair<double,char>

Aside from the implicit conversion bonus of it, if you didn't use make_pair you'd have to do

one = pair<int,int>(10,20)

every time you assigned to one, which would be annoying over time...

Peary answered 14/2, 2012 at 1:39 Comment(6)
Actually, the types should be deduced at compile time without the need to specify.Mongol
@Tor Yeah, I know how to use both of them, I was just curious if there was a reason for std::make_pair. Apparently it is just for convenience.Shinn
I think you can do one = {10, 20} nowadays but I don't have a C++11 compiler handy to check it.Expectorate
Also note that make_pair works with unnamed types, including structs, unions, lambdas, and other doodads.Straightaway
"The difference is ... you need to specify the types of both elements" This answer is only correct for C++14 and earlier versions.Balkin
You could indeed do this in C++11 one = {10, 20}; But not this: one = std::pair{10,20); // works in C++17Lutist
R
61

Class template arguments could not be inferred from the constructor before C++17

Before C++17 you could not write something like:

std::pair p(1, 'a');

since that would infer template types from the constructor arguments, you had to write it explicitly as:

std::pair<int,char> p(1, 'a');

C++17 makes that syntax possible, and therefore make_pair redundant.

Before C++17, std::make_pair allowed us to write less verbose code:

MyLongClassName1 o1;
MyLongClassName2 o2;
auto p = std::make_pair(o1, o2);

instead of the more verbose:

std::pair<MyLongClassName1,MyLongClassName2> p{o1, o2};

which repeats the types, and can be very long.

Type inference works in that pre-C++17 case because make_pair is not a constructor.

make_pair is essentially equivalent to:

template<class T1, class T2>
std::pair<T1, T2> my_make_pair(T1 t1, T2 t2) {
    return std::pair<T1, T2>(t1, t2);
}

The same concept applies to inserter vs insert_iterator.

See also:

Minimal example

To make things more concrete, we can observe the problem minimally with:

main.cpp

template <class MyType>
struct MyClass {
    MyType i;
    MyClass(MyType i) : i(i) {}
};

template<class MyType>
MyClass<MyType> make_my_class(MyType i) {
    return MyClass<MyType>(i);
}

int main() {
    MyClass<int> my_class(1);
}

then:

g++-8 -Wall -Wextra -Wpedantic -std=c++17 main.cpp

compiles happily, but:

g++-8 -Wall -Wextra -Wpedantic -std=c++14 main.cpp

fails with:

main.cpp: In function ‘int main()’:
main.cpp:13:13: error: missing template arguments before ‘my_class’
     MyClass my_class(1);
             ^~~~~~~~

and requires instead to work:

MyClass<int> my_class(1);

or the helper:

auto my_class = make_my_class(1);

which uses a regular function instead of a constructor.

Difference for std::reference_wrapper

This comment mentions that std::make_pair unwraps std::reference_wrapper while the constructor does not, so that's one difference. TODO example.

Tested with GCC 8.1.0, Ubuntu 16.04.

Resentment answered 7/1, 2017 at 12:26 Comment(4)
"C++17 makes that syntax possible, and therefore make_pair redundant." - Why is it that std::make_pair did not become deprecated in C++17?Spannew
@Spannew I'm not sure, possible reason is that it creates no trouble so no need to break old code? But I'm not familiar with C++ committee rationale, ping me if you find something.Resentment
One useful thing that I have come across is that being able to specify the types with std::make_pair<T1, T2>(o1, o2) prevents the user from making the mistake of passing types o1 or o2 that can't implicitly be cast to T1 or T2. For instance passing a negative number to an unsigned int. -Wsign-conversion -Werror will not catch this error with std::pair constructor in c++11 however it will catch the error if std::make_pair is used.Taps
make_pair unwraps reference wrappers, so it's different from CTAD actually.Galasyn
B
53

As @MSalters replied above, you can now use curly braces to do this in C++11 (just verified this with a C++11 compiler):

pair<int, int> p = {1, 2};
Bearcat answered 24/2, 2014 at 18:28 Comment(0)
S
26

There is no difference between using make_pair and explicitly calling the pair constructor with specified type arguments. std::make_pair is more convenient when the types are verbose because a template method has type deduction based on its given parameters. For example,

std::vector< std::pair< std::vector<int>, std::vector<int> > > vecOfPair;
std::vector<int> emptyV;

// shorter
vecOfPair.push_back(std::make_pair(emptyV, emptyV));

 // longer
vecOfPair.push_back(std::pair< std::vector<int>, std::vector<int> >(emptyV, emptyV));
Silicle answered 14/2, 2012 at 1:49 Comment(1)
Even shorter: vecOfPair.emplace_back(emptyV, emptyV);Mesonephros
B
21

It's worth noting that this is a common idiom in C++ template programming. It's known as the Object Generator idiom, you can find more information and a nice example here.

Edit As someone suggested in the comments (since removed) the following is a slightly modified extract from the link in case it breaks.

An Object Generator allows creation of objects without explicitly specifying their types. It is based on a useful property of function templates which class templates don't have: The type parameters of a function template are deduced automatically from its actual parameters. std::make_pair is a simple example that returns an instance of the std::pair template depending on the actual parameters of the std::make_pair function.

template <class T, class U>
std::pair <T, U> 
make_pair(T t, U u)
{
  return std::pair <T, U> (t,u);
}
Bereft answered 15/3, 2013 at 14:35 Comment(1)
@duck Actually && since C++11.Conrad
O
5

make_pair creates an extra copy over the direct constructor. I always typedef my pairs to provide simple syntax.
This shows the difference (example by Rampal Chaudhary):

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair( 1, sample) );
    //map.insert( std::pair<int,Sample>( 1, sample) );
    return 0;
}
Ona answered 28/11, 2013 at 13:40 Comment(5)
I am pretty sure that the extra copy will be elided in all cases, if the optimization settings of the compiler are high enough.Symptomatic
Why would you ever want to rely on compiler optimizations for correctness?Slither
I get the same results with both versions, and with std::move just inside insert and/or around what would be a reference to sample. It is only when I change std::map<int,Sample> to std::map<int,Sample const&> that I reduce the number of constructed objects, and only when I delete the copy constructor that I eliminate all copies (obviously). After making both of those changes, my result includes one call to the default constructor and two calls to the destructor for the same object. I think I must be missing something. (g++ 5.4.1, c++11)Conker
FWIW I agree that optimization and correctness should be completely independent, as this is exactly the kind of code you write as a sanity check after different optimization levels produce inconsistent results. In general I would recommend emplace instead of insert if you're just constructing a value to insert immediately (and you don't want extra instances.) It's not my area of expertise, if I can even say I have one, but the copy/move semantics introduced by C++11 have helped me a lot.Conker
I believe I am encountering exactly the same issue and after debugging for about the entire evening, I finally came here.Servitor
D
2

starting from c++11 just use uniform initialization for pairs. So instead of:

std::make_pair(1, 2);

or

std::pair<int, int>(1, 2);

just use

{1, 2};
Disharmony answered 14/3, 2019 at 10:4 Comment(1)
{1, 2} can be used to initialize a pair, but does not commit for type pair. I.e. when using auto you have to commit to a type on the RHS: auto p = std::pair{"Tokyo"s, 9.00};.Gnni
H
0

I'm never going to use std::make_pair again because it's "broken" (from my perspective - at least it's unintuitive). Consider the following example

You have a backing storage, or a string pool of

using Id = int;
std::vector<std::string> string_pool;
std::map<std::string_view, Id> str_to_id_map;

If you attempt to insert into std::map like so;

str_to_id_map.insert(std::make_pair(string_pool.back(), 42));

This will fail, spectacularly. It will first create a copy of the std::string which then will be turned into a std::string_view - and now, that string_view is pointing to "anywhere". It might work, it might not work. I tested this just recently.

However, if you instead use

str_to_id_map.insert({string_pool.back(), 42});

This will correctly be deduced by the Compiler Gods as a string_view over the string_pool contents and will work - no intermediary copy of a std::string, turned into a string_view

My lesson or takeaway I gather from this is - never, ever, ever, ever use std::make_pair.

Haldi answered 13/9, 2023 at 16:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.