Reputation: 2345
Given the following function template from "The C++ Programming language 4th edition":
template <typename TT, typename A>
unique_ptr<TT> make_unique(int i, A && a)
{
return unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};
}
I find it difficult to understand what that actually does,
a is definitely an rvalue and therefore the make_unique function seem to allocate its content on the heap and holding that address in a unique_ptr so we won't have to worry about deleting it. but, what does the standard library forward function does? (I guess it has something to do with a being rvalue) I tried reading at C++ documentation but I don't seem to understand that properly. would love to get a good explanation from a more experienced C++ programmer.
thanks!
Upvotes: 1
Views: 367
Reputation: 17708
Hmmm... I'm pretty sure this isn't given as a work-around implementation of the future std::make_unique
, but anyway, what the function does is pretty easy to understand, though it requires you to have prior knowledge of new C++11 features.
template <typename TT, typename A>
unique_ptr<TT> make_unique(int i, A && a)
{
return unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};
}
First of all make_unique
is a function template, I really hope you already know that, as the following would require that you have at least the most basic knowledge on what templates does and how templates work.
Now to the non-trivial parts. A && a
there is a function parameter. Specifically, a
is the function parameter whose type is A&&
which is an r-value reference. With its type being a template type parameter, we can deduce its type from whatever the caller passes as an argument to a
. Whenever we have r-value reference and argument type deduction, special deduction rules and reference collapsing kicks-in and we have a so-called "universal reference" which is particularly useful for perfect forwarding functions.
Whenever we have a universal reference (a
in our case), we will almost always want to preserve its original "l-valueness" or "r-valueness" whenever we want to use them. To have this kind of behavior, we should almost always use std::forward
(std::forward<A>(a)
). By using std::forward
, a variable originally passed as an l-value remains an l-value and a variable originally passed as an r-value remains an r-value.
After that, things are just simple
return unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};
Notice the use of the braces. Instead of using parentheses, it is using C++11's uniform initialization syntax of calling constructors. With new TT{ i, std::forward<A>(a) }
, you are dynamically allocating an object of type TT
with the given parameters inside the braces. With unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};
, you are creating a unique_ptr<TT>
whose parameter is the one returned by the dynamic allocation. The unique_ptr<TT>
object now then returned from the function.
Upvotes: 2
Reputation: 18905
Due to template argument deduction and reference collapsing rules you cannot know if a
is a rvalue reference or a lvalue reference. std::forward
passes the argument to the TT contrustor exactly as it was passed to make_unique. Scott Meyers calls A&&
a universal reference, because it can be a lvalue ref or an rvalue ref, depended on what is passed to make_unique.
If you pass an rvalue Foo
to make_unique
, std::forward
passes an rvalue reference.
If you pass an lvalue Foo
to make_unique
, std::forward
passes an lvalue reference.
make_unique(1, Foo()); // make_unique(int, A&&) -> rvalue ref
Foo f;
make_unique(1, f); // make_unique(int, A&&&) -> make_unique(int, A&) -> lvalue ref
make_unique(1, std::move(f)); // make_unique(int, A&&&&) -> make_unique(int, A&&) -> rvalue ref
Upvotes: 1