6

To demo move semantics, I wrote the following example code, with an implicit constructor from int.

struct C {
  int i_=0;
  C() {}
  C(int i) : i_( i ) {}
  C( const C& other) :i_(other.i_) {
    std::cout << "A copy construction was made." << i_<<std::endl;
  }
  C& operator=( const C& other) {
    i_= other.i_ ;
    std::cout << "A copy assign was made."<< i_<<std::endl;
    return *this;
  }
  C( C&& other ) noexcept :i_( std::move(other.i_)) {
    std::cout << "A move construction was made." << i_ << std::endl;
  }
  C& operator=( C&& other ) noexcept {
    i_ = std::move(other.i_);
    std::cout << "A move assign was made." << i_ << std::endl;
    return *this;
  }
};

And

auto vec2 = std::vector<C>{1,2,3,4,5};
cout << "reversing\n";
std::reverse(vec2.begin(),vec2.end());

With output

A copy construction was made.1
A copy construction was made.2
A copy construction was made.3
A copy construction was made.4
A copy construction was made.5
reversing
A move construction was made.1
A move assign was made.5
A move assign was made.1
A move construction was made.2
A move assign was made.4
A move assign was made.2

Now, the reverse shows the 2 two swaps (each using one move assign and two move constructs), but why are the temporary C objects created from the initializer list not possible to move from? I thought I had an initializer list of integers, but I'm now wondering if what I have in between is an initializer list of Cs, which can't be moved from (as its const). Is this a correct interpretation? - What's going on?

Live demo

Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • 2
    Pretty sure [this](http://stackoverflow.com/questions/8193102/initializer-list-and-move-semantics) is the issue your are experiencing. – NathanOliver Jan 27 '17 at 13:43
  • @NathanOliver, that's about moving from an initializer list of C, but I thought I had a list of integers, feeding construction of temporary C objects. – Johan Lundberg Jan 27 '17 at 13:45
  • A vector of type `std::vector` expects an `std::initializer_list` so your list of int's is constructed into a temporary list of C and it is that list of C's that is being copied from. At least that is what I reason the code to have to do. – NathanOliver Jan 27 '17 at 13:50
  • Initializer list construction does copying for non-aggregates. That just the way it is, sorry. – AndyG Jan 27 '17 at 13:51
  • `std::vector` doesn't have constructor which takes `std::initializer_list` (but it has one for `std::initializer_list`). – Jarod42 Jan 27 '17 at 13:51
  • Possible duplicate of [initializer\_list immutable nature leads to excessive copying](http://stackoverflow.com/questions/27194519/initializer-list-immutable-nature-leads-to-excessive-copying) – Tomilov Anatoliy Jan 28 '17 at 08:21
  • No orient, that info was already a premise to the question. If you read the answers you'll see the difference. – Johan Lundberg Jan 28 '17 at 09:39

3 Answers3

9

I thought I had an initializer list of integers, but I'm now wondering if what I have in between is an initializer list of Cs, which can't be moved from (as its const). Is this a correct interpretation?

This is correct. vector<C> does not have an initializer_list<int> constructor or even an initializer_list<T> constructor for some template parameter T. What it does have is an initializer_list<C> constructor - which is built up from all the ints you pass in. Since the backing of initializer_list is a const array, you get a bunch of copies instead of a bunch of moves.

Barry
  • 286,269
  • 29
  • 621
  • 977
3

As detailed in my comment you get the copies because a vector of type std::vector<C> expects an std::initializer_list<C> so your list of int's is constructed into a temporary list of C's and it is that list of C's that is being copied from.

One way you could get around this though is to make a helper function. Using something like

template <typename T, typename Y>
std::vector<T> emplace_list(std::initializer_list<Y> list)
{
    std::vector<T> temp;
    temp.reserve(list.size());
    for (const auto& e: list)
        temp.emplace_back(e);
    return temp;
}

int main()
{
    auto vec2 = emplace_list<C>({1,2,3,4,5});
}

You can avoid making copies of the elements from the list because you directly construct them into the vector using emplace_back. If the compiler applies NRVO then you do not even have a move of the vector out of the function. See this live example for g++'s full output. Do note I have the constructor printing just so you can see it is the only one called.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
1

Thinking a bit more. Here's the self answer:

std::vector<C> does not have a constructor of initializer_list<int>, or event of T convertible to C. It does have the constructor

vector( std::initializer_list<T> init,
    const Allocator& alloc = Allocator() );

So, the initializer_list argument list will be an initializer_list<C>. Said constructor can do nothing else than copy from the initializer list since they are immutable (used to say const, but the effect here is the same, it can't be move from).

Oh, and that's also what NathanOliver wrote in a comment as I wrote this.

Community
  • 1
  • 1
Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • The only thing that is `const` in the code you posted was the allocator. The real reason copies are made is because list initialization only copies (for non-aggregates, which is what `C` is because you've included user-defined constructors), period. – AndyG Jan 27 '17 at 13:54
  • 2
    @AndyG That's not right. List initialization doesn't only copy (`X x{std::move(y)}` is fine), and `std::initializer_list` is implicitly const. – Barry Jan 27 '17 at 14:04
  • @Barry: I meant construction from a `std::initializer_list`, is there an easier way to state that? – AndyG Jan 27 '17 at 14:26
  • @AndyG Nope. It's a bit confusing because there's list-initialization, there's *initializer-list*, and there's `std::initializer_list`, and they're all different, though related, things. Just gotta be careful. – Barry Jan 27 '17 at 15:17