28

In my understanding, the sole reason for the existence of std::make_pair and std::make_tuple is that you don't have to write the types by yourself as they are automatically deduced. In C++17, we have template argument deduction for class templates, which allows us to simply write

std::pair p(1, 2.5); // C++17

instead of

auto p = std::make_pair(1, 2.5); // C++11/14

The situation for std::tuple is analogous. This leads to the following question: In C++17, is there a situation in which it is beneficial to use std::make_pair and std::make_tuple instead of using the constructors of std::pair and std::tuple?

Please, consider just pure C++17 code (i.e. no need for backward compatibility with C++14) and assume that everyone is familiar with this C++17 feature.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
s3rvac
  • 9,301
  • 9
  • 46
  • 74

1 Answers1

45

In C++1z, is there a situation in which it is beneficial to use std::make_pair and std::make_tuple instead of using the constructors of std::pair and std::tuple?

There are always fun exceptions to every rule. What do you want to happen to std::reference_wrapper?

int i = 42;
auto r = std::ref(i);

pair p(i, r);                 // std::pair<int, std::reference_wrapper<int> >
auto q = std::make_pair(i,r); // std::pair<int, int&>

If you want the latter, std::make_pair is what you want.


Another situation cames in generic code. Let's say I have a template parameter pack that I want to turn into a tuple, would I write this?

template <typename... Ts>
auto foo(Ts... ts) {
    return std::tuple(ts...);
}

Now the intent of that code is to probably to get a std::tuple<Ts...> out of it, but that's not necessarily what happens. It depends on Ts...:

  • if Ts... is a single object that is itself a tuple, you get a copy of it. That is, foo(std::tuple{1}) gives me a tuple<int> rather than a tuple<tuple<int>>.
  • if Ts... was a single object that is a pair, I get a tuple of those elements. That is, foo(std::pair{1, 2}) gives me a tuple<int, int> rather than a tuple<pair<int, int>>.

In generic code, I would stay away from using CTAD for types like tuple because it's never clear what you're going to get. make_tuple doesn't have this problem. make_tuple(tuple{1}) is a tuple<tuple<int>> and make_tuple(pair{1, 2}) is a tuple<pair<int, int>> because that's what you asked for.


Additionally, since std::make_pair is a function template, you can pass that into another function template that maybe wants to do something:

foo(std::make_pair<int, int>);

This doesn't seem super useful, but somebody somewhere is using it to solve a problem - and you can't just pass std::pair there.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Similarly to `std::reference_wrapper`, you may add `make_pair` overloads (with your custom type) without fully specialized `pair` for your type. – Jarod42 May 09 '17 at 16:58
  • Regarding passing constructors as function templates -- at least not until someone proposes that `std::direct_init` or whatever it was called... and then `make_from_tuple` would go away, too. – Kerrek SB May 09 '17 at 17:12
  • @KerrekSB I don't think [it's related](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0271r0.html)? `direct_init` wouldn't work on a `tuple` (unless `T` had a `tuple` constructor). – Barry May 09 '17 at 17:17
  • @Barry: Couldn't you just do `pair p(i, *r);` if you want a language reference? Indeed, it would make your intent a lot more clear than the `make_pair` example; I would have expected it to use a `reference_wrapper` too. – Nicol Bolas May 09 '17 at 17:51
  • 2
    @NicolBolas No, that'd give you `pair` (assuming you mean like `r.get()`) – Barry May 09 '17 at 17:59
  • @Barry: You're right, you couldn't use it to replace passing `make_pair` as a metacallback. Never mind. – Kerrek SB May 09 '17 at 18:07
  • Clearly we need to be able to pass pointers-to-constructors around. And the deduction guides for `pair` should unpack `reference_wrapper`s. Too late now. C++17, here she comes! – Yakk - Adam Nevraumont May 09 '17 at 18:28
  • @Yakk They considered making the deduction guide unwarp and decided against it. – T.C. May 09 '17 at 19:06
  • @T.C. Do you know how come? – Barry May 09 '17 at 19:13
  • Could you provide some details on how exactly the type-deduction differs between template constructors and template functions? I'm surprised that there's a difference. – Kyle Strand May 09 '17 at 19:28
  • @Barry Not beyond what the paper says. – T.C. May 09 '17 at 19:34
  • @KyleStrand template constructors have deduction guides. I am uncertain if C++17 added them to template functions, but C++14 did not have them. – Yakk - Adam Nevraumont May 09 '17 at 19:52
  • @Yakk Oh, I didn't realize from the above conversation (or what I've read of the proposal) that "deduction guides" is a standardese term. Since I don't fully understand how deduction works for template constructors, would you (or Barry or TC) be willing to take a look at the ambiguity described at the end of [this answer](http://stackoverflow.com/a/39087312/1858225) and confirm or refute my understanding of the issue? – Kyle Strand May 09 '17 at 20:01
  • @KyleStrand That's not an answer. That's a blog post. And `auto var2 = Variable(var);` would be of type `Variable` – Barry May 09 '17 at 20:08
  • @Barry I'm not sure I understand the criticism; it's a bit long, but not terribly long compared to other longish answers on the site, and all of the points addressed are directly in response to either the question itself or other answers that are incorrect (since so many of them say, essentially, "not only does C++ not support this, it *can't* support this, because [some example]"). – Kyle Strand May 09 '17 at 20:34
  • 1
    @KyleStrand I stopped at "requires the use of `auto`". That's...just not true. – T.C. May 10 '17 at 09:35
  • @T.C. You are absolutely correct, and thank you for pointing that out. Looking at the proposal again, this is completely obvious, so I'm not sure why I thought `auto` was necessary, and frankly I feel like a fool. I've completely revised the answer; fortunately Coliru supports 7.1, which has GCC 7.1.0, so I was able to actually try out my code examples this time. – Kyle Strand May 10 '17 at 16:49
  • Isn't the second example kind of moot? I mean, you could pass a lambda which constructs a pair, rather than `std::make_pair`. – einpoklum Sep 17 '19 at 08:47