I'm trying to implement std::vector
all by myself for some exercising purposes. My question is about the push_back(..)
methods. There are two overloads of this method as in the following.
void push_back(const value_type& value);
void push_back(value_type&& value);
At first, I implemented them in two different functions. For the former one, I choose to copy construct the new element with the given value. And for the latter one, I choose to move construct the new element with the given value. Here are the implementations of mine:
template<class T>
void Vector<T>::push_back(const value_type& value)
{
if(size() == capacity()) // Size is about to surpass the capacity
grow(nextPowerOf2(capacity()), true); // Grow and copy the old content
new(data + sz++) value_type(value); // Copy construct new element with the incoming one
}
template<class T>
void Vector<T>::push_back(value_type&& value)
{
if(size() == capacity()) // Size is about to surpass the capacity
grow(nextPowerOf2(capacity()), true); // Grow and copy the old content
new(data + sz++) value_type(std::move(value)); // Move construct new element with the incoming
}
After then, I realized that I could merge these two functions in a single method that takes a universal reference such as in the following code:
template<class T>
template<class U>
void Vector<T>::push_back(U&& value)
{
if(size() == capacity()) // Size is about to surpass the capacity
grow(nextPowerOf2(capacity()), true); // Grow and copy the old content
// Construct new element with the incoming
new(data + sz++) value_type(std::forward<U>(value));
}
I think this single method handles all the things that I was trying to do with two different methods. Is there a thing that I'm missing? Will this operation bother me later? I don't seek backward compatibility, so you can omit it.
The second version is not equivalent to the first one.
In the first you take an argument of type "reference to T
", and in the second U
can be of any type, not related to T
.
So the second version is actually more an emplace_back
than a push_back
. It will take any value and try to construct an instance of T
using that (though normally emplace_back
takes a pack of arguments, for more flexibility).
You could restrict U
to a type compatible with T
using SFINAE, but then the code might become more complicated than just having two separate overloads.
template<class T>
template<class U, typename std::enable_if_t<std::is_convertible_v<std::decay_t<U>, T>, int> = 0>
void Vector<T>::push_back(U&& value)
{
// . . .
stdlibc++ for example has two separate overloads for vector::push_back
, but there push_back(T&&)
simply delegates to emplace_back
. So maybe you could do the same.