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';
}
}
CodePudding user response:
I think I found a way to solve this issue. I used the same "technique" as with the regular insert
methods. The full implementation of my_class
is this. Unfortunately, I could not make a single insert
method.
template <typename T>
class my_class {
private:
T m_data;
private:
template <typename U>
void do_some_more_work(U&& x) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
m_data = std::forward<U>(x);
}
template <typename U>
void do_some_work(U&& x) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
do_some_more_work(std::forward<U>(x));
}
public:
template <typename U>
void insert(U&& x) noexcept {
std::cout << __PRETTY_FUNCTION__ << '\n';
do_some_work(std::forward<U>(x));
}
template <typename U>
void insert_vector(U&& v) noexcept {
using elem_t = typename std::remove_reference_t<U>::value_type;
// this is used here for testing purposes.
static_assert(std::is_same_v<elem_t, my_struct>);
std::cout << __PRETTY_FUNCTION__ << '\n';
for (std::size_t i = 0; i < v.size(); i) {
if constexpr (std::is_same_v<std::vector<elem_t>, U>) {
do_some_work(std::forward<T>(v[i]));
}
else {
do_some_work(v[i]);
}
}
}
};
CodePudding user response:
[...] Unfortunately, I could not make a single insert method.
Providing a trait for checking the passed template container class is a specialization for the std::vector
, you can combine the two functions easily as follows in c 17:
// Template Traits for checking `std::vector`
template<typename> struct is_std_vector final : std::false_type {};
template<typename T, typename... Args>
struct is_std_vector<std::vector<T, Args...>> final : std::true_type {};
Now the member function can be combined as
template <typename U>
void insert(U&& v) /* noexcept */
{
if constexpr (is_std_vector<std::decay_t<U>>::value)
{
using elem_t = typename std::remove_reference_t<U>::value_type;
std::cout << __PRETTY_FUNCTION__ << '\n';
for (std::size_t i = 0; i < v.size(); i) {
if constexpr (std::is_same_v<std::vector<elem_t>, U>) {
do_some_work(std::forward<T>(v[i]));
}
else {
do_some_work(v[i]);
}
}
}
else
{
std::cout << __PRETTY_FUNCTION__ << '\n';
do_some_work(std::forward<U>(v));
}
}