9

I have a (third-party) class which is non-copyable. I'd like to initialize an array of them. Here's my best attempt:

#include <array>

class Thing
{
public:
  explicit Thing(int) {}
  Thing(const Thing&) = delete;
};

int main()
{
  std::array<Thing, 1> things{{{100}}}; // error here
};

GCC 4.7.2 says:

error: converting to ‘std::array::value_type {aka Thing}’ from initializer list would use explicit constructor ‘Thing::Thing(int)’

OK, but that's exactly what I want--to use the explicit constructor. How can I express that? If I actually invoke the constructor myself, then I get an error about the copy constructor being deleted. And I can't use std::move() because Thing is not movable (and I can't modify it).

The only alternative I've found so far is https://stackoverflow.com/a/15962814/4323 but this is undesirable because it's a bunch of extra code plus I need to cast the "storage" everywhere I use it (or keep a separate pointer to it, which adds indirection I don't want).

I want a solution that gives maximum performance when actually using the Things without a lot of ugly boilerplate.

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • You could add an explicit constructor that takes `initializer_list` ? – M.M Apr 08 '14 at 03:55
  • The Standard says that for `std::array`, `T` must be MoveConstructible and MoveAssignable – M.M Apr 08 '14 at 04:09
  • The solution in the linked thread doesn't look too painful. You have to have a level of indirection anyway (I don't think accessing `things[n]` for `std::array` is any different to accessing `things[n]` where `Things *things;` points into the storage that you placement new'd into) – M.M Apr 08 '14 at 04:12
  • @MattMcNabb: I expect it is different, because the address of the array itself is known at compile time, but the address stored in a pointer is probably not understood by the compiler in the same way. I'd probably end up just casting the aligned storage to the array type at the call site (in a function of course). I hadn't heard that the value_type of std::array must be movable...it seems to work just fine when not, so long as the value_type's constructor is not explicit. – John Zwinck Apr 08 '14 at 04:29

3 Answers3

1

I tried adding the default move ctor and move assignment operator, changed the initialization a bit and it compiles:

#include <array>

class Thing
{
public:
  explicit Thing(int) {}
  Thing(const Thing&) = delete;
  Thing(Thing&&) = default;
  Thing& operator=(Thing&&) = default;
};

int main()
{
    std::array<Thing, 1> things {{ Thing(100) }}; // error gone
}

EDIT: I'd missed the "third-party" part. Sorry if this doesn't help :)

yati sagade
  • 1,355
  • 9
  • 24
  • 2
    Yes, making the class movable fixes it, as does making the constructor not explicit. But I can't do either, because it's not my class. I guess I could wrap the class with one having an implicit constructor.... – John Zwinck Apr 08 '14 at 09:30
  • Adding a move constructor does not fix the case where the new[] operator is used. For example: `auto things = new Thing[1] { 100 };` will not work, and neither will `auto things = new Thing[1] { Thing { 100 } };`. – Alastair May 28 '15 at 14:24
1

Yet again, C++17's guaranteed copy elision comes to the rescue: an expression like Thing{100} no longer creates an object but merely specifies how some other object (your array element) is to be created.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Do you know of any compiler that can compile my original code now? I tried a few things in https://godbolt.org/ (GCC trunk, Clang 5.0), but none of them worked, even with `-std=c++17`. – John Zwinck Sep 18 '17 at 02:41
  • You do have to add the `Thing`: then you get https://wandbox.org/permlink/a6QUxzY3O0xN1GJ6. – Davis Herring Sep 18 '17 at 03:02
-2

You can use a std::vector:

std::vector<Thing> things;
things.reserve(1);
things.emplace_back(100);

or for just one element boost::optional:

boost::optional<Thing> thing;
thing.emplace(100);
R1tschY
  • 3,032
  • 1
  • 24
  • 30