0

I am trying to wrap my head around variadic templates, move semantics, (maybe perfect forwarding?), and learn how to pass variable down to different functions.

Lets say, there is a class Foo which holds an array of Elem (templated) class instances. These array elements regularly gets re-initialized (hence the Elem.init function).

Below I am trying to initialize an Elem with three large strings (or any other large objects which implements move semantics) to exactly the place where they will be stored: In the Foo's array, at a certain location.

Is that even possible to move them to the Foo's vector location right away? Or will it be always a "copy semantics" instead of a move.

Overall, I am trying to avoid copying large stack allocated strings around and somehow have them be in the target vector's location. Not sure what would be the best way to do that.

(All i got is a segmentation fault at the moment)

Thank you for reading! Code below online: https://onlinegdb.com/HJc7U_jIO

#include <iostream>
#include <vector>
#include <string>

class Elem
{
public:

    void init(std::string&& s1, std::string&& s2, std::string&& s3)
    {
        s1 = std::move(s1);
        s2 = std::move(s2);
        s3 = std::move(s3);

        std::cout << mS1 << mS2 << mS3;
    }

private:

    std::string mS1;
    std::string mS2;
    std::string mS3;

};


template <class T>
class Foo
{
public:

    template <typename... Args>
    void add(Args... args)
    {
        mElements[mNextFreeIndex].init(args...);
        mNextFreeIndex++;
    }

private:
    std::vector<T> mElements;
    int mNextFreeIndex;
};


int main()
{
    Foo<Elem> foo;

    foo.add(std::move("Apple"), std::move("Pear"), std::move("Carrot"));        //passing 3 parameters

    return 0;
}
Avi
  • 1,066
  • 1
  • 15
  • 37

1 Answers1

1

To properly "debug" these issues, you may want to write a class with copy & move constructors, destructor and copy & move assignment operators which declare their being executed. Then you can tell what happens with the values as they get passed around and/or created.

Anyway, you're calling the add() method with three std::string&& arguments. But - inside the function, once they are bound to args..., they are now lvalues! You either need to use std::move() in your add() method:

template <typename... Args>
void add(Args... args)
{
    mElements[mNextFreeIndex].init(std::move(args)...);
    mNextFreeIndex++;
}

or apply forwarding:

template <typename... Args>
void add(Args&&... args)
{
    mElements[mNextFreeIndex].init(std::forward<Args>(args)...);
    mNextFreeIndex++;
}

On a mostly unrelated note, I would recommend reading this: "Parameter" vs "Argument" regarding the use of these two terms.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • one more question: so either i use std::move everywhere, or either std::forward, so should i mix them? – Avi Apr 20 '21 at 19:01
  • 1
    @Avi: There's isn't some general rule here that I could give you. Forwarding is for those cases where you need the same code to work for both rvalue and lvalue references. And - if you find yourself moving a lot or forwarding a lot, on and on and on, then - maybe you should simply strive for less layers of code. – einpoklum Apr 20 '21 at 20:49