6

I want to construct an std::vector with some elements having these elements constructed by some particular constructor rather than the default constructor. In other words I want to emplace the elements while constructing the vector. How can I do that?

Consider this:

struct Item
{
    Item(double) {}
    Item(const Item&) = delete;
    Item(Item&&) = delete;
};
std::vector<Item> vv(10, 3.14); // Fails! Tries to copy while I want to emplace.
Vahagn
  • 4,670
  • 9
  • 43
  • 72
  • Perhaps a vector is the wrong container for you, perhaps you should use a [`std::array`](http://en.cppreference.com/w/cpp/container/array) instead? Or perhaps you could elaborate about the *actual* problem you have? *Why* you came up with solution you want help with? This is a very typical [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) type of question. – Some programmer dude Oct 11 '17 at 10:56
  • 1
    @Someprogrammerdude Actually, not `std::vector` nor `std::array` but the built-in dynamic array is the data structure that fits best to the purpose (dynamic but fixed size, no modifications). But the built-in dynamic arrays don't support this *emplace-while-constructing* functionality. – Vahagn Oct 11 '17 at 11:04
  • Well considering that "dynamic arrays" (I assume you mean [*variable-length arrays*](https://en.wikipedia.org/wiki/Variable-length_array)?) doesn't exist in standard C++ that's not a choice either. Or do you mean `new[]`? And perhaps the *original problem* you have could be solved some other way? – Some programmer dude Oct 11 '17 at 11:07
  • 2
    @Someprogrammerdude I meant `new[]`. The *original problem* is to have a collection of predefined (but known at runtime, not compile-time) amount of `std::thread`s that are initialized with the same lambda. Thus copy is forbidden and it would be nice to avoid move as well (as ideally I shouldn't have to move). – Vahagn Oct 11 '17 at 17:56

2 Answers2

6

You can use the vector constructor that takes two iterators. For example you could use iterators from a vector<double>.

std::vector<double> init(10, 3.14);
std::vector<Item> vv(init.begin(), init.end());

Live demo.

Or you could write your own custom iterator class to do the initialization:

struct ItemInitializer {
  int index;
  double value;

  using value_type = double;
  using difference_type = int;  
  using pointer = const double*;
  using reference = const double&;
  using iterator_category = std::forward_iterator_tag;

  ItemInitializer() : index(0), value(0.0) {}
  ItemInitializer(int index, double v = 0.0) : index(index), value(v) {}

  bool             operator!=(const ItemInitializer& other) const { return other.index != index; }
  ItemInitializer& operator++() { index++; return *this; }
  ItemInitializer  operator++(int) { ItemInitializer ret(index, value); index++; return ret; }
  const double&    operator*() const { return value; }  
  const double*    operator->() const { return &value; } 
};

std::vector<Item> vv(ItemInitializer{0, 3.14}, ItemInitializer{10});

Live demo.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • The first code block is good. But what's the point of the second? I couldn't see much difference between it and ```std::vector vv; vv.assign(10, Item{3.14});``` – atakli Sep 16 '22 at 10:57
  • 1
    @atakli `std::vector vv; vv.assign(10, Item{3.14});` will not compile if `Item` is not copyable or movable. The first code block involves allocating a temporary `vector` so perhaps in some performance critical code the second code block would be an optimization. – Chris Drew Sep 20 '22 at 10:31
5

Your Item class doesn't support copies nor moves. This will prevent most operations on std::vector from compiling, including std::vector::reserve and std::vector::resize. If you really have such a class, you might want an std::vector<std::aligned_storage_t<sizeof(Item), alignof(Item)>> instead.

If you can add a move constructor to Item, you can create your helper function instead (as the constructor overload that you're using is defined in terms of copying). Note that the version below only works for unary constructors.

template <typename T, typename Arg>
auto make_vector(std::size_t n, Arg&& arg)
{
    std::vector<T> result;
    result.reserve(n);

    for(std::size_t i = 0; i < n; ++i)
        result.emplace_back(arg);

    return result;
}

Usage:

auto vec = make_vector<Item>(10, 3.14);
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • Nononon the fold expression with a comma was great, put it back! It's not even in the edit history :/ – Bartek Banachewicz Oct 11 '17 at 10:55
  • @BartekBanachewicz: it was doing the wrong thing :P – Vittorio Romeo Oct 11 '17 at 10:55
  • Oh well. It made me realize that a comma is actually an operator :D – Bartek Banachewicz Oct 11 '17 at 10:56
  • @VittorioRomeo I don't need that operations on `std::vector` to compile (as mentioned in the comment to the question). In fact I need only constructor (the one I want) and the destructor. Your suggestion is actually the workaround that I have in the code for now։) The drawback is: *defauct constructor + reserve (that can cause reallocation)* instead of *the constructor I want*. The workaround would have no drawback (except of more lines of code) if there were a constructor of `std::vector` that just reserves. – Vahagn Oct 12 '17 at 05:31
  • Answer by chris drew is correct in my opinion. Vector allows constructing from the iterators and it emplaces the element. – Kavar Rushikesh Feb 18 '22 at 17:22