std::vector init with braces call copy constructor twice

1.6k views Asked by At

Why when I init std::vector with braces

std::vector<TS> vec {ts1, ts2};

Compiler call twice copy constructor operator? On the other hand - with push_back it called only once.

#include <iostream>
#include <vector>
using namespace std;

struct TS{
    TS(){
        cout<<"default constructor\n";
    }

    TS(const TS &other) {
        cout<<"Copy constructor\n";
    }

    TS(TS &&other) noexcept{
        cout<<"Move constructor\n";
    }

    TS& operator=(TS const& other)
    {
        cout<<"Copy assigment\n";
        return *this;
    }

    TS& operator=(TS const&& other) noexcept
    {
        cout<<"Move assigment\n";
        return *this;
    }

    ~TS(){
        cout<<"destructor\n";
    }

};

int main() {
    TS ts1;
    TS ts2;
    cout<<"-----------------------------------------\n";
    std::vector<TS> vec {ts1, ts2};
    //vec.push_back(ts1);
    //vec = {ts1, ts2};
    cout<<"-----------------------------------------\n";



    return 0;
}

http://ideone.com/qcPG7X

3

There are 3 answers

1
AudioBubble On BEST ANSWER

From what I understand, initializer_lists pass everything by const-reference. It is probably not safe to move from one. The initializer_list constructor of a vector will copy each of the elements.

Here are some links: initializer_list and move semantics

No, that won't work as intended; you will still get copies. I'm pretty surprised by this, as I'd thought that initializer_list existed to keep an array of temporaries until they were move'd.

begin and end for initializer_list return const T *, so the result of move in your code is T const && — an immutable rvalue reference. Such an expression can't meaningfully be moved from. It will bind to an function parameter of type T const & because rvalues do bind to const lvalue references, and you will still see copy semantics.

Is it safe to move elements of a initializer list?

initializer_list only provides const access to its elements. You could use const_cast to make that code compile, but then the moves might end up with undefined behaviour (if the elements of the initializer_list are truly const). So, no it is not safe to do this moving. There are workarounds for this, if you truly need it.

Can I list-initialize a vector of move-only type?

The synopsis of in 18.9 makes it reasonably clear that elements of an initializer list are always passed via const-reference. Unfortunately, there does not appear to be any way of using move-semantic in initializer list elements in the current revision of the language.

questions regarding the design of std::initializer_list

From section 18.9 of the C++ Standard:

An object of type initializer_list provides access to an array of objects of type const E. [ Note: A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 8.5.4. Copying an initializer list does not copy the underlying elements. — end note ]

I think the reason for most of these things is that std::initializer_list isn't actually a container. It doesn't have value semantics, it has pointer semantics. Which is made obvious by the last portion of the quote: Copying an initializer list does not copy the underlying elements. Seeing as they were intended solely for the purpose of initializing things, I don't think it's that surprising that you don't get all the niceties of more robust containers such as tuples.

If I understand the last part correctly, it means that two sets of copies are needed since initializer_list does not copy the underlying elements. (The previous quote is only relevant if you attempt to use an initializer_list without copying out the elements.)

What is the underlying structure of std::initializer_list?

No, you can't move from the elements of an initializer_list, since elements of an initializer_list are supposed to be immutable (see the first sentence of the paragraph quoted above). That's also the reason why only const-qualified member functions give you access to the elements.


If you want, you can use emplace_back:

vec.emplace_back(TS());
vec.emplace_back(TS());
vec.push_back(std::move(ts1));
vec.push_back(std::move(ts2));
3
Lightness Races in Orbit On

Because you have two elements there.

0
Igor Polkovnikov On

In the code you have provided, with Microsoft compiler, the copy constructor for TS when it is in the vector initializer list will be called only once!

TS ts1;  // contructor called
std::vector<TS> vec {ts1};  // copy constructor called. Only once.

If you create an object in the initializer list, it will do both:

std::vector<TS> vec {TS()}; // constructor, then copy constructor called

In the second case, if you have a number of objects, all constructors will be called first, then all copy contructors.