1

My constructor originally took a std::vector<> but I couldn't figure out how to get a braced list to initialize it. I got it working after changing to an std:initializer_list<>. I found two ways: 1) passing the initializer_list in as a parameter to the array constructor (commented out in code below) and 2) using the std::copy algo (as seen in code below).

Now, I need to create this object with a std::vector<> and can't figure out how to convert it to the initializer_list. I could make a second constructor that takes a vector, but as an exercise I'd like to get this working with one constructor if possible.

Any ideas?

class One: public Base {
public:
  One( Two* ptwo_in, std::initializer_list<Three> ilthree ) :
    ptwo( ptwo_in )//,
    //athree_( ilthree )
  {
    athree_.reserve( ilthree .size() );
    std::copy( ilthree .begin(), ilthree .end(), athree_.begin() );
  }

  Two*               ptwo_;
  std::vector<Three> athree_;
};

  // Works fine:

  new One( &two, { Three( args ),
                   Three( args ),
                   Three( args ), } )



  // Doesn't work:

  std::vector<Three>* pathreeSomething
  athreeOther.push_back( new One( ptwoLocal, *pathreeSomething ) );
Swiss Frank
  • 1,985
  • 15
  • 33
  • "but I couldn't figure out how to get a braced list to initialize it." What do you mean? It should just work. What have you tried? – Nikos C. Oct 07 '20 at 10:45
  • @NikosC. On that third line of code it works with replacing `initializer_list` with `vector`, true. But I don't want to pass around a whole vector as an arg. So I try `std::vector& ilthree`. And that fails. To be clear, any idea how I can iniitialize a constructor's REFERENCE (or pointer!) to a vector with a braced list of the objects in question? – Swiss Frank Oct 07 '20 at 12:04

1 Answers1

1

On that third line of code it works with replacing initializer_list with vector, true. But I don't want to pass around a whole vector as an arg.

You're going to construct a vector anyway (One::athree_). So just move the vector passed to the constructor rather than copy it:

class One: public Base {
public:
    One(Two* ptwo_in, std::vector<Three> ilthree)
        : ptwo{ptwo_in}
        , athree_{std::move(ilthree)}
    { }

private:    
    Two* ptwo_;
    std::vector<Three> athree_;
};

This is a common pattern in C++. Pass by non-const value and use move semantics to avoid copies:

One one{some_ptr, {Three{args}, Three{args}, Three{args}}};

or:

std::vector<Three> vec{ ... };

// moves 'vec' to the ctor parameter, the ctor then moves it to its member
One one{some_ptr, std::move(vec)};

No unnecessary copies that way.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • thx. How does the compiler, when it generates code for the CALL to the constructor, know that the constructor initializer list does a move, unless that initializer list is visible to the compilation? (E.g, constructor is in the header?) And even when it IS I'm surprised that generation of the code for the CALL to the constructor goes by anything more than the constructor's signature? – Swiss Frank Oct 08 '20 at 02:30
  • 1
    @SwissFrank It doesn't know. You're responsible for implementing move-aware functions correctly. – Nikos C. Oct 08 '20 at 06:08
  • OK, but in your example, One one ... { initializer }... wouldn't the compiler normally try to delete the objects in the { initializer }? After the move, now the variable in the object could keep a copy for a long time after the constructor returns, so with the move the compiler should NOT try to delete the objects in the { initializer }... How does it know not to? – Swiss Frank Oct 08 '20 at 06:43
  • 1
    @SwissFrank `One one{ ... }` is somewhat similar to `One one( ... )` but has some benefits like disallowing narrowing conversions. It is not an initializer list! Perhaps I should have used `()` instead to not confuse you :-P See: https://stackoverflow.com/questions/18222926/why-is-list-initialization-using-curly-braces-better-than-the-alternatives but keep in mind that opinions vary here. Some people prefer `()`, some prefer `{}`. – Nikos C. Oct 08 '20 at 06:50
  • not confused by the {} instead of (). I'll try to ask a different way. `{Three{args}, Three{args}, Three{args}}` creates a vector in the arguments area of the new stack frame, right? Who normally destructs, that caller I think? So after the ctor does a move, that vector created by `{Three{args}, Three{args}, Three{args}}` is no longer valid and no longer needs destruction, right? So whoever normally destructs it doesn't need to any more, but how do they know that? – Swiss Frank Oct 08 '20 at 06:56
  • 1
    @SwissFrank After the vector is moved from `ilthree` to `athree_`, `ilthree` is destructed normally when the ctor returns. However, since it was moved from, it contains no objects, so the dtor is destroying an empty vector, which is cheap. The vector being empty after the move is not actually required by the standard (it only says "valid but unspecified state",) but it doesn't make sense for a vector to be in any other state than empty (unless the move couldn't be performed for some reason and you got a copy instead, like when the parameter was `const`, for example, which prevents moving.) – Nikos C. Oct 08 '20 at 07:06
  • C: OK.... I think I understand... Although spec says "valid but unspecified" I guess in the cast of vector, in practice, that means a vector such that it CAN legally be destructed without affecting the vector I moved to. So, when compiling the caller, the compiler need not know there's a move, because destroying the vector as usual still works. And the calling code can't refer to that vector, so it need not know that despite the static initializer the vector is suddenly empty. – Swiss Frank Oct 09 '20 at 00:31
  • One more question sir. If the caller had made a local variable vector and initialized with the brace-enclosed initializer, that variable is in the caller's stack frame. If this was passed in to the ctor with the `move()`, a COPY of that local variable is made for the ctor's stack frame, right? Then, the move will make that COPY empty, leaving the caller's local variable alone, right? To avoid that copy normally I'd pass by reference, but when the caller takes a reference to a vector, the braced initializer is no longer accepted and I can't see the reason why? – Swiss Frank Oct 09 '20 at 00:38
  • 1
    @SwissFrank No, no copy is made. `vec` is moved into the constructor argument. After that, `vec` is a valid object with an unspecified state :-) – Nikos C. Oct 09 '20 at 05:38