lefticus / cpp_weekly

The official C++ Weekly Repository. Code samples and notes of future / past episodes will land here at various times. PR's will be accepted in some cases.
The Unlicense
663 stars 24 forks source link

From C++ Weekly Ep 404 : Code which should avoid move does a copy with std::vector #391

Open Indra5196 opened 1 week ago

Indra5196 commented 1 week ago

Hello Jason,

This issue refers to Ep 404 of C++ weekly series.

As suggested in the video, use of std::array's initializer list constructor does not invoke the copy / move constructor of the class. However if std::vector's initializer list constructor is used, it invokes copy constructor. I am not able to understand the reasoning behind it, and I would like you to shed some light on it.

Code:

#include <iostream>
#include <vector>
#include <array>

class A {

public:

    int member;
    // Parameterized constructor
    A() {
        std::cout << "Default constructor called" << std::endl;
    }

    A(int val) : member(val) {
        std::cout << "Parameterized constructor called with value: " << val << std::endl;
    }

    // Copy constructor
    A(const A& other) : member(other.member) {
        std::cout << "Copy constructor called. Copied value: " << other.member << std::endl;
    }

    // Move constructor
    A(A&& other) noexcept : member(other.member) {
        other.member = 0; // Reset the moved-from object's state (optional)
        std::cout << "Move constructor called. Moved value: " << member << std::endl;
    }

    // Destructor
    ~A() {
        std::cout << "Destructor called. Object with value " << member << " destroyed." << std::endl;
    }

    // Getter method
    int getMember() const {
        return member;
    }
};

int main() {
    auto make_a = [](const int val) {
        A a;
        a.member = val;
        return a;
    };
    // std::array<A, 2> arr_a{make_a(5), make_a(6)};
    std::vector<A> vec_a{make_a(5), make_a(6)};
}

Compiled and executed on onlinegdb with C++14/17/20/23 with default warnings and optimizations

Thank you in Advance Jason. Your big fan!!

fcolecumberri commented 1 week ago

Nothing in the documentation of vector https://en.cppreference.com/w/cpp/container/vector/vector lead me to think it would move the elements.

Not just that, but moving the elements would be very confusing. Imagine the next code:

std::string a = "a";
std::string b = "b";

std::vector<std::string> v {a, b};

it's quite clear that a and b would be copied.

Maybe if a constructor like vector(std::initializer_list<T&&> init, const Allocator& alloc = Allocator() ); existed (but it is not part of the standard).

also you could just:

std::vector<A> vec_a;
vec_a.reserve(2);
vec_a.emplace_back(make_a(5));
vec_a.emplace_back(make_a(6));
Indra5196 commented 1 day ago

I guess we are completely deviating from the point. 1st of all, It makes sense that a & b are copied as they are lvalues.

My question was, that in the above example, why std::array does not invoke copy / move whereas std::vector does? I don't think std::array has any sort specialized container either which enables such behavior.

LB-- commented 1 day ago

The explanation is that std::array does not have any constructors. The braced initializer list syntax therefore directly initializes the array elements. By contrast, std::vector can't do that, it must take a std::initializer_list which is a read-only type - you literally cannot move from std::initializer_list elements because they are const. If you want to create a constructor capable of moving from a list of elements, use std::array instead of std::initializer_list. Since std::vector has no such constructor, your best alternative is using the iterator pair constructor with an iterator type that moves values out of the source range, for example std::move_iterator