14

I have the following very simple class:

class Foo 
{
public:
  Foo() {}
  Foo(const Foo&) = delete;
  Foo(Foo&&) {}

  void operator=(const Foo&) = delete;
  void operator=(Foo&&) {}

  void dump() const {}
};

The class is move constructible and assignable but isn't copy constructible and assignable.

I'd like to initialize a vector of Foo elements using vector's initializer list.

std::vector<Foo> vf = { Foo() };

The compiler complains because code has to use the deleted copy constructor. Could anybody explain, why the move construct is not used in this case, and why copy of the object is needed?

The following also requires the copy constructor and does not work either:

std::vector<Foo> vf = { std::move(Foo()) };

On the other hand, this works fine (the move constructor is called):

std::vector<Foo> vf;
vf.push_back(Foo());

Thanks for the explanation... :)

Update:

A suggested this post explains my question.

Furthermore let's consider the following code (together with class Foo above):

class Bar {
public:
    Bar(std::initializer_list<Foo> _l) {
        std::cout << "Bar::Bar()" << std::endl;
        for (auto& e : _l)
            e.dump();
    }
};

int main() {
    Bar b { Foo() };
    return 0;
}

This produces the following output (compiled with C++11):

Foo::Foo()
Bar::Bar()
Foo::dump()
Foo::~Foo()

It can be seen, that the initializer list is not actually filled with the 'copy of the objects' stated between the braces. This might be not true from C++14.

Community
  • 1
  • 1
Peter
  • 141
  • 1
  • 6

3 Answers3

11

It's specifically because you use an initializer list that the copy-constructor is used. The initializer list is initialized with a copy of the object, and then passed to the vector.

If you read the linked reference, from C++14 it even explicitly say

... each element is copy-initialized ... from the corresponding element of the original initializer list

Emphasis mine

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • "The initializer list is initialized with a copy of the object" - is that really the case? Isn't the problem here that "begin and end for initializer_list return const T *" (as per http://stackoverflow.com/a/8193157/388661) and so you cannot _move_ items out of the initializer list? – davmac Apr 05 '16 at 08:38
  • Hmm, just did a quick test. Initializing a vector with an initializer list containing a single object does indeed cause the copy constructor to be called _twice_. Seems to me like there's a language design issue here! Upvoted. – davmac Apr 05 '16 at 08:41
  • @davmac Also from the reference: "An object of type `std::initializer_list` is a lightweight proxy object that provides access to an **array** of objects of type `const T`. " Because an "array" is used elements needs to be copied, and it's what prohibits moving. – Some programmer dude Apr 05 '16 at 08:41
  • Got it, though I'd trust a quote from the actual language standard more. In my eyes it would be better if the implementation were allowed to construct the objects in place (within the backing array) and was (at least potentially) allowed to move objects out, thus eliding two copies. I don't see any technical reason why it couldn't be this way. – davmac Apr 05 '16 at 08:48
  • This answer while incidentally correct has a wrong premise. Copy initialization (even in the standard) refers to copy *or* move. You might notice the suspicious absence of "move initialization" on the very same page that you linked. – user6160478 Apr 05 '16 at 10:51
  • @user6160478 which doesn't matter in this case because `std::initializer_list` only provides `const` access to its elements, meaning they cannot be moved from – underscore_d Jul 18 '16 at 10:21
0

std::initializer_list doesn't work for move-only types. See this question for more details.

Fortunately, there's a dead easy fix for you:

std::vector<foo> vf (1);

This will initialize the vector with 1 default-constructed foo.

Community
  • 1
  • 1
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • Okay, maybe my example code was too simplified. The purpose of this code was not purely just create single element in `vf`, but to demonstrate my question. – Peter Apr 06 '16 at 09:17
0

On general the requirements that are imposed on the elements of a std::vector is that the element type is a complete type and meets the requirements of Erasable. As such there's no problem with your object.

However, initializing with an initializer list as such:

std::vector<Foo> vf = { Foo() };

Requires that the temporary object Foo() in the list be copied into the vector. Consequently, the compiler complains because you've already made your object non-copyable.

You can accomplish what you want in the following way;

std::vector<Foo> vf(1);
101010
  • 41,839
  • 11
  • 94
  • 168