Home > Back-end >  Move constructor for vec3 class
Move constructor for vec3 class

Time:08-16

I'm trying to improve my cpp lately, and I want to write my own vec3 class to represent a 3D vector and point. I want it to hold 3 values of the same type and have some standard operations like addition and things...

I'm trying to learn more about move semantics and I'd like to create a move constructor that would take 3 literal ints like this: vec3<int> a(1, 2, 3); But this shows me error that more than one instance of constructor matches the argument list. Here is my code:

template <typename T>
class vec3 {
    T x, y, z;

    // standard copy ctor
    vec3(T _x, T _y, T _z): x(_x), y(_y), z(_z) {}
    // move ctor for rvalue references like vec3(1, 2, 3)
    vec3(T &&_x, T &&_y, T &&_z): x(_x), y(_y), z(_z) {}

    vec3<T> operator (const vec3<T> &vec) {
        // I don't want to create 3 int's just to copy them
        return vec3<T>(this.x   vec.x,
                       this.y   vec.y,
                       this.z   vec.z);
    }
};

int main() {
    vec3<int> b(1, 2, 3); // more than one instance of constructor
                          // "vec3<T>::vec3 [with T=int]" matches the argument list:C/C  (309)

    int x, y, z = 5;
    vec3<int> c(x, y, z); // OK, it matches copy ctor
}

I'd like to know if my understanding of copy ctors is correct. Can I use it for moving simple, temporary int values into functions? Or is move semantics only for more complex types like std::vector? Is my way of thinking about moving temporary values directly into constructor correct?

I'd greatly appreciate help in this matter, and gladly read more about this topic. Any good articles with use cases would be helpful, as reading cpp reference doesn't help me a lot right now.

CodePudding user response:

You didn't write a move constructor, nor a copy constructor. They would have to look like vec3(vec3 &&) and vec3(const vec3 &) respectively. You shouldn't write them manually if possible (including in this case), the compiler will generate them for you. The same applies to copy/move assignment, and to the destructor.

Regarding your element-wise constructors, you have multiple options:

  1. Use only the first constructor: vec3(T _x, T _y, T _z), and remove the other one. Note that you forgot to move the elements; it should look like this:

    vec3(T _x, T _y, T _z): x(std::move(_x)), y(std::move(_y)), z(std::move(_z)) {}
    

    Moving scalar types is the same as copying them, but for more complex types (if you decide to use them with your vector), moving should be faster.

  2. Use the second constructor and remove the first, but then you also need 7 other constructors (23 total), to cover all combinations of const T & and T && parameter types. When the parameter type is T &&, you need to std::move it:

    vec3(const T &_x, const T &_y, const T &_z): x(_x), y(_y), z(_z) {}
    vec3(T &&_x, const T &_y, const T &_z): x(std::move(_x)), y(_y), z(_z) {}
    vec3(const T &_x, T &&_y, const T &_z): x(_x), y(std::move(_y)), z(_z) {}
    // And so on, 5 move constructors.
    
  3. But having to write so many constructors is not practical. You can replace them with a single template (look up "perfect forwarding"):

    template <typename A, typename B, typename C>
    vec3(A &&_x, B &&_y, C &&_z)
        : x(std::forward<A>(_x)), y(std::forward<B>(_y)), z(std::forward<C>(_z))
    {}
    

    Despite those looking like the usual rvalue references, they're also forwarding references, meaning they can take both lvalues and rvalues. std::forward then acts as a conditional std::move, which only happens if you passed an rvalue (and the reference resolved to &&, instead of &).

As for what option to choose: (2) is not practical. I would start with (1), since it's easier to write. If you decide it's not fast enough, you can replace it with (3).


Note that you forgot to const-qualify your operator . It should be:

vec3<T> operator (const vec3<T> &vec) const

CodePudding user response:

The error means that the two constructors that you wrote have the same input and calling syntax. In vec3<int> b(1, 2, 3) c does not what constructor to call as the only difference is that the second one takes values by reference but you cannot specify that. So try to make the constructors different in how they take input. Ex. You can Make the second constructor take pointers to the numbers and not reference.

  • Related