3

I have a class with a non trivial constructor:

class mystream
{
public:
    mystream(size_t buffersize,size_t index) : buffersize(buffersize),index(index){}

    size_t buffersize;
    size_t index;
};

The mystream instance has an unique id which corresponds with its position in a vector of the managing class:

class mystreammanager
{
public:
   mystreammanager() : streams(8,1024, /* WHAT TO DO HERE ??? */ )
   {
   }

  std::vector<mystream> streams;
};

How can I construct the vector and initialize its elements with an ascending value for the index?

5 Answers 5

8

A succinct, clear and easy-to-debug way to do this is to defer the construction of the vector to a static class function:

class mystreammanager
{
public:
   mystreammanager() : streams{ generate_streams(1024, 8) }
   {
   }

private:
  static std::vector<mystream> generate_streams(size_t buffersize, size_t qty)
  {
    std::vector<mystream> result;
    result.reserve(qty);
    for(size_t i = 0 ; i < qty ; ++qty) {
      result.emplace_back(buffersize, i); 
    }
    return result;
  } 

  std::vector<mystream> streams;
};

This is optimally efficient because:

  1. RVO causes the vector to be constructed in-place
  2. avoiding list-initialisation means no redundant copies.
Sign up to request clarification or add additional context in comments.

7 Comments

This is awesome, but not usable if you work in a team as it is also unreadable as an average developer does not expect a method call into the instance itself during initialising (at least, I do not). But thx
@MartinSchlott: If your team can't read a straightforward function, then you need a new team.
@MikeSeymour maybe you are right. But one has to work with what he has :-) . The Problem is IMHO that that situation can escalate when you got more than one vector you have to take special care of in your managing class. I like thinks happening near by. Also it has not really an advantage over a loop AFAIK.
@MartinSchlott if you have more than one vector you have to take special care of then I think that is even more reason to defer construction to a separate function.
The advantage of deferring construction to a function is that RVO gives you optimal performance while the logic is written only once (for example if you have 2 overloaded constructors).
|
3

Just use a loop:

mystreammanager() {
    streams.reserve(8);
    for (int i = 0; i < 8; ++i) {
        streams.emplace_back(1024, i);
    }
}

3 Comments

That's what I wanted to avoid.
@MartinSchlott Why? It's the clearest way of doing it.
At the end, I will do it like you suggest, because it is really the clearest way. But I had to accept @Jarod42 answer, because it is the exact answer to my question but also shows the unpleasant effect.
3

Compile time version :)

Requires c++14 but could surely be adapted for c++11

#include <cstddef>
#include <vector>
#include <utility>

class mystream
{
public:
  mystream(size_t buffersize,size_t index) : buffersize(buffersize),index(index){}

  size_t buffersize;
  size_t index;
};

template<size_t... Indexes>
std::initializer_list<mystream>    mystream_maker_impl(std::index_sequence<Indexes...>)
{
  return {{1024, Indexes}...};
}

template<size_t N>
std::initializer_list<mystream>    mystream_maker()
{
  return mystream_maker_impl(std::make_index_sequence<N>());
}

class mystreammanager
{
public:
  mystreammanager() : streams(mystream_maker<8>())
  {
  }

  std::vector<mystream> streams;
};

5 Comments

Awesome. Can you tell me which part is C++14? I cannot accept this answer as I stated C++11 in my question.
std::index_sequence and std::make_index_sequence (en.cppreference.com/w/cpp/utility/integer_sequence) are C++14, but unless my imagination is playing tricks on me i've seen implementation of those multiple times on stackoverflow.
I tested it on the latest XCode and it only accepted it with compiling with -std=C++14 or -std=gnu++14 not c++11. As I use VS2013 too I do not expect that to compile on all my platforms. Beside that, it would be the solution I was searching for, as near as possible at a compile time solution.
@MartinSchlott As i said on the previous comment you have to reimplement std::index_sequence and std::make_index_sequence if you want to compile this on c++11 take a look at stackoverflow.com/questions/17424477/… :)
unfortunately std::initializer_list is not move-aware, so the stream type will need to be copyable. In my view, this is a terrible oversight in the design of std::initializer_list
2

You can do:

class mystreammanager
{
public:
   mystreammanager() : streams{{1024, 0}, {1024, 1}, {1024, 2}, {1024, 3},
                               {1024, 4}, {1024, 5}, {1024, 6}, {1024, 7}}
   {
   }

  std::vector<mystream> streams;
};

But doing a loop seems safer/simpler.

Comments

1

I used the answer from @RichardHodges as I was not happy with my first choice. I came up with this template:

template<class T,class ...Args> std::vector<T> generate_with_index(size_t qty,Args ...args)
{
    std::vector<T> result;
    result.reserve(qty);
    for(size_t i = 0 ; i < qty ; ++qty)
        result.emplace_back(i, args...);
    return result;
}

It helps me avoiding redundancy. From a theoretical view I like @Drax solution most as it do the most work during compile time.

1 Comment

in which case you'll want to eliminate redundant copies like this: template<class T,class ...Args> std::vector<T> generate_with_index(size_t qty,Args&& ...args) { std::vector<T> result; result.reserve(qty); for(size_t i = 0 ; i < qty ; ++qty) result.emplace_back(i, std::forward<Args>(args)...); return result; }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.