40

I mean why does std::make_tuple exist? I know that there are situations where the function reduces the amount of characters you have to type because you can avoid template parameters. But is it the only reason? What makes std::tuple special that the function exists while other class templates haven't such function? Is it only because you may use std::tuple more often in such situations?


Here are two examples where std::make_tuple reduces the amount of characters:

// Avoiding template parameters in definition of variable.
// Consider that template parameters can be very long sometimes.
std::tuple<int, double> t(0, 0.0); // without std::make_tuple
auto t = std::make_tuple(0, 0.0);  // with std::make_tuple

// Avoiding template parameters at construction.
f(std::tuple<int, double>(0, 0.0)); // without std::make_tuple
f(std::make_tuple(0, 0.0));         // with std::make_tuple

But like written above, you don't have a function like this for many other class templates.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
JojOatXGME
  • 3,023
  • 2
  • 25
  • 41
  • 3
    There are various similar functions, but the most obvious direct comparison is [`std::make_pair`](http://en.cppreference.com/w/cpp/utility/pair/make_pair). I think a rule of thumb might be that they exist if you might want to construct these objects simply as part of another expression, to call a function, etc. – BoBTFish Dec 09 '15 at 14:02
  • 1
    What other classes would you like a `make_*` function for? We are already getting make_shared, make_unique, etc. – Marc Glisse Dec 09 '15 at 14:06
  • 1
    The only difference is type deduction. But type deduction can be useful, so that's why. – Nathan Cooper Dec 09 '15 at 14:07
  • If you try to make equivalent code without it, you'll need a scope and a call to `std::move`. It would get very ugly. Consider: `auto z = foo(std::make_tuple(x,y));` versus `{ auto j = std::tuple(x,y); auto z = foo(std::move(j)); }` Oops, `z` is now out of scope. But I need `j` out of scope. Ack. – David Schwartz Dec 09 '15 at 14:11
  • It is also a convenient function when you want to write python-like statements :) Here's an example from [N3337](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf) _"20.4.2.4 Tuple creation functions [tuple.creation] item #8"_ : `tie(i, ignore, s) = make_tuple(42, 3.14, "C++");` – maddouri Dec 09 '15 at 14:12
  • 5
    Not all types can be named. E.g. `make_tuple([&](){ f(x); }, std::mem_fn(&X::foo))`. – Kerrek SB Dec 09 '15 at 14:15
  • 3
    `make_tuple` fits into the family of `make_tuple`, `tie` and `forward_as_tuple`, which respectively give you prvalues, lvalues and forwarded-values. – Kerrek SB Dec 09 '15 at 14:16
  • Ok, thank you all. I have used pairs only after getting them from a map. I haven't noticed `std::make_pair` yet. The function `std::make_shared` has a optimisation and I thought `std::make_unique` in C++14 would be added to be consistent with `make_shared`. Mostly I have worked with types like `std::vector` yet and they haven't such functions. I think this mean there is no benefit beside less code. – JojOatXGME Dec 09 '15 at 14:20
  • The close votes don't make much sense to me, as my answer clearly shows there is nothing opinion based about the rationale for having `make_tuple`. I don't think the answer is obvious and so this looks like a good question to me. – Shafik Yaghmour Dec 09 '15 at 16:00

4 Answers4

44

Because you cannot use argument deduction for constructors. You need to write explicitly std::tuple<int, double>(i,d);.

It makes it more convenient for creating a tuple and passing it to another function in one-shot.

takes_tuple(make_tuple(i,d)) vs takes_tuple(tuple<int,double>(i,d)).

One less place to change when the type of i or d changes, especially if there were possible conversions to between the old and new types.

If it were possible to write std::tuple(i,d);, make_* would (probably) be redundant.

(Don't ask why here. Maybe for similar reasons why syntax A a(); does not invoke a default constructor. There are some painful c++ syntax peculiarities.)

UPDATE NOTE: As Daniel rightly notices, c++17 will be enhanced, so that template argument deduction will work for constructors, and such delegation will become obsolete.

luk32
  • 15,812
  • 38
  • 62
  • 15
    Since **C++17**, it will be possible to use **class template deduction**, i.e., to write `std::tuple t(1, 1.0, 'a');` or `auto t = std::tuple(1, 1.0, 'a');`. See, e.g., [here](http://en.cppreference.com/w/cpp/language/class_template_deduction) for details. – Daniel Langr Jun 28 '17 at 12:08
  • Included your update note into the answer. Thanks for calling it. – luk32 Jun 29 '17 at 13:25
  • 4
    @luk32 "and such delegation will become obsolete" I disagree: `std::make_tuple` will use `X&` if you wrap something in `std::ref()` call, which will still be useful in C++17, as [deduction guides for `std::tuple`](http://en.cppreference.com/w/cpp/utility/tuple/deduction_guides) don't seem to handle such case. – Dev Null Sep 22 '17 at 01:48
9

We can find a rationale for why we need make_tuple and the various other make_* utilities in proposal N3602: Template parameter deduction for constructors which says (emphasis mine):

This paper proposes extending template parameter deduction for functions to constructors of template classes. The clearest way to describe the problem and solution is with some examples.

Suppose we have defined the following.

vector<int> vi1 = { 0, 1, 1, 2, 3, 5, 8 }; 
vector<int> vi2; template<class Func> 
    class Foo() { 
        public: Foo(Func f) : func(f) {} 
        void operator()(int i) { os << "Calling with " << i << endl; f(i); } 
        private: 
        Func func;
    };

Currently, if we want to instantiate template classes, we need to either specify the template parameters or use a "make_*" wrapper, leverage template parameter deduction for functions, or punt completely:

pair<int, double> p(2, 4.5); 
auto t = make_tuple(4, 3, 2.5); 
copy_n(vi1, 3, back_inserter(vi2)); // Virtually impossible to pass a lambda to a template class' constructor
for_each(vi.begin(), vi.end(), Foo<???>([&](int i) { ...}));

Note, the proposal is being tracked via EWG issue 60.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • @black well the EWG issue I just added to my answer suggests some issues, but the work was encouraged. The EWG active list was not updated from Kona yet, so perhaps this has changed. – Shafik Yaghmour Dec 09 '15 at 19:35
4

I think that a clever use of those kind of function is to be passed as parameters.
Something like that:

std::bind_front(&std::make_tuple, 1, "test", true);

It could be usefull since, if i am not wrong, we can not directly call constructors.

auto obj = Object::Object(3); // error: cannot call constructor ‘Object::Object’ directly [-fpermissive]
2

Only for template argument deduction. However, here's a (contrived) example where this is required for using a lambda:

class A
{
public:
    template<typename F>
    A(const std::tuple<F> &t)
    {
        // e.g.
        std::get<0>(t)();
    }
};

class B : public A
{
public:
     B(int i) : A(std::make_tuple([&i]{ ++i; }))
     {
         // Do something with i
     }
};

std::tuple<decltype([&i]{ ++i; })>([&i]{ ++i; }) cannot be used because the two lambda expressions have different types. A polymorphic wrapper like std::function adds runtime overhead. A named class with user-defined operator () would work (which may also need to be a friend of B, depending on the contents of the operator's body). That's what we used in ye olden days before C++11.

Arne Vogel
  • 6,346
  • 2
  • 18
  • 31