I've been reading about std::forward
and I think I understand it well, but I don't think I understand it well enough to use it proficiently enough.
I have a template class that implements a container and has a method called insert
. I wanted this method to accept constant references and rvalue references so that if the inserted element is a rvalue reference, it is moved into the container, not copied. For this, I first overloaded the insert
method like this:
template <typename U>
void do_some_work(U&& x) noexcept
{
m_data = std::forward<U>(x);
}
void insert(const T& x)
{
do_some_work(x);
}
void insert(T&& x) {
do_some_work(std::forward<T>(x);
}
The problem is, these two functions now have to call an "inner" function that implements the assertion. If the functions are small, I guess this is not a problem, but if they are large, it is best to use templates, like this
template <typename U>
void insert(U&& x)
{
do_some_work(std::forward<U>(x);
}
QUESTION 1: Is this correct?
Now, I want to do the same but with std::vector
.
void insert_vector(const std::vector<T>& v) noexcept {
for (std::size_t i = 0; i < v.size(); ++i) {
do_some_work(v[i]);
}
}
void insert_vector(std::vector<T>&& v) noexcept
{
for (std::size_t i = 0; i < v.size(); ++i)
{
do_some_work(std::forward<T>(v[i]));
}
}
QUESTION 2: How do I collapse the insert_vector
functions into a single one so that the call to do_some_work
is done with
- rvalue references when the vector is a rvalue reference (specified with
std::move
, for example), - constant reference when the vector is not given as an rvalue reference, like in
(4)
of the MWE below. The following does not work for me
template <typename U>
void insert_vector(U&& v) noexcept
{
for (std::size_t i = 0; i < v.size(); ++i)
{
do_some_work(std::forward<T>(v[i]));
}
}
Here is a minimum working example:
#include <iostream>
#include <vector>
template <typename T>
class my_class
{
private:
T m_data;
private:
template <typename U>
void do_some_more_work(U&& x) noexcept {
m_data = std::forward<U>(x);
}
template <typename U>
void do_some_work(U&& x) noexcept {
do_some_more_work(std::forward<U>(x));
}
public:
void insert(const T& x) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
do_some_work(x);
}
void insert(T&& x) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
do_some_work(std::forward<T>(x));
}
void insert_vector(const std::vector<T>& v) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
for (std::size_t i = 0; i < v.size(); ++i) {
do_some_work(v[i]);
}
}
void insert_vector(std::vector<T>&& v) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
for (std::size_t i = 0; i < v.size(); ++i) {
do_some_work(std::forward<T>(v[i]));
}
}
};
struct my_struct {
my_struct() noexcept = default;
my_struct(int v) noexcept : m_v(v) { }
my_struct(const my_struct& s) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
m_v = s.m_v;
}
my_struct(my_struct&& s) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
m_v = std::move(s.m_v); // not need, but whatever
s.m_v = -1;
}
my_struct& operator= (const my_struct& s) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
m_v = s.m_v;
return *this;
}
my_struct& operator= (my_struct&& s) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
m_v = std::move(s.m_v); // not need, but whatever
s.m_v = -1;
return *this;
}
int m_v;
};
int main() {
my_class<my_struct> mc;
std::cout << "===========================================\n";
{
std::cout << "-------------------------------------------\n";
std::cout << "(1.1)\n";
my_struct s{3};
std::cout << s.m_v << '\n';
mc.insert(s);
std::cout << s.m_v << '\n';
}
{
std::cout << "-------------------------------------------\n";
std::cout << "(1.2)\n";
const my_struct s{3};
std::cout << s.m_v << '\n';
mc.insert(s);
std::cout << s.m_v << '\n';
}
std::cout << "===========================================\n";
{
std::cout << "-------------------------------------------\n";
std::cout << "(2.1)\n";
mc.insert(my_struct{5});
}
{
std::cout << "-------------------------------------------\n";
std::cout << "(2.2)\n";
my_struct s{5};
std::cout << s.m_v << '\n';
mc.insert(std::move(s));
std::cout << s.m_v << '\n';
}
std::cout << "===========================================\n";
{
std::cout << "-------------------------------------------\n";
std::cout << "(3.1)\n";
std::vector<my_struct> v(5);
for (std::size_t i = 0; i < v.size(); ++i) { v[i] = my_struct(i); }
for (const auto& s : v) { std::cout << s.m_v << ' '; } std::cout << '\n';
mc.insert_vector(v);
for (const auto& s : v) { std::cout << s.m_v << ' '; } std::cout << '\n';
}
{
std::cout << "-------------------------------------------\n";
std::cout << "(3.2)\n";
const std::vector<my_struct> v = []() {
std::vector<my_struct> v(5);
for (std::size_t i = 0; i < v.size(); ++i) { v[i] = my_struct(i); }
return v;
}();
for (const auto& s : v) { std::cout << s.m_v << ' '; } std::cout << '\n';
mc.insert_vector(v);
for (const auto& s : v) { std::cout << s.m_v << ' '; } std::cout << '\n';
}
std::cout << "===========================================\n";
{
std::cout << "-------------------------------------------\n";
std::cout << "(4)\n";
std::vector<my_struct> v(5);
for (std::size_t i = 0; i < v.size(); ++i) { v[i] = my_struct(i); }
for (const auto& s : v) { std::cout << s.m_v << ' '; } std::cout << '\n';
mc.insert_vector(std::move(v));
for (const auto& s : v) { std::cout << s.m_v << ' '; } std::cout << '\n';
}
}