26

In many languages, there are generators that help to initialize collections. In C++, if one wants to initialize a vector uniformly, one can write:

std::vector<int> vec(10, 42); // get 10 elements, each equals 42

What if one wants to generate different values on the fly? For example, initialize it with 10 random values, or consecutive numbers from 0 to 9? This syntax would be convenient, but it does not work in C++11:

int cnt = 0;
std::vector<int> vec(10, [&cnt]()->int { return cnt++;});

Is there a nice way to initialize a collection by iterative function calls? I currently use this ugly pattern (not much more readable/short than a loop):

std::vector<int> vec;
int cnt = 0;
std::generate_n(std::back_inserter(vec), 10, [&cnt]()->int { return cnt++;});

There is a thing that would help, and it would explain the lack of the first constructor. I can imagine an iterator that takes a function and number of calls, so that the constructor

vector ( InputIterator first, InputIterator last);

would be applicable. But I did not find anything like this in the standard library. Did I miss it? Is there another reason why the first constructor did not make it to the standard?

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Roman Shapovalov
  • 2,785
  • 1
  • 25
  • 30
  • 1
    I don't find that pattern so ugly, but I like this question. I am curious to learn whether there is another reason apart from avoiding class interface bloat. – Gorpik Sep 20 '12 at 11:45
  • 3
    On the bright side, C++ is the perfect language for *you* to write an iterable object that implements your desired semantics! – Kerrek SB Sep 20 '12 at 11:46

5 Answers5

15

Sadly, there is no standard facility to do this.

For your specific example, you could use Boost.Iterator's counting_iterator like this:

std::vector<int> v(boost::counting_iterator<int>(0),
    boost::counting_iterator<int>(10));

Or even with Boost.Range like this:

auto v(boost::copy_range<std::vector<int>>(boost::irange(0,10)));

(copy_range will basically just return std::vector<int>(begin(range), end(range)) and is a great way to adopt full range construction to exisiting containers that only support range construction with two iterators.)


Now, for the general purpose case with a generator function (like std::rand), there is the function_input_iterator. When incremented, it calls the generator and saves the result, which is then returned when dereferencing it.

#include <vector>
#include <iostream>
#include <cmath>
#include <boost/iterator/function_input_iterator.hpp>

int main(){
  std::vector<int> v(boost::make_function_input_iterator(std::rand, 0),
      boost::make_function_input_iterator(std::rand,10));
  for(auto e : v)
    std::cout << e << " ";
}

Live example.

Sadly, since function_input_iterator doesn't use Boost.ResultOf, you need a function pointer or a function object type that has a nested result_type. Lambdas, for whatever reason, don't have that. You could pass the lambda to a std::function (or boost::function) object, which defines that. Here's an example with std::function. One can only hope that Boost.Iterator will make use of Boost.ResultOf someday, which will use decltype if BOOST_RESULT_OF_USE_DECLTYPE is defined.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 2
    Hmm. Local structs cannot be used as template arguments in C++03, and in C++11 you have lambdas. – Konrad Rudolph Sep 20 '12 at 11:58
  • @Konrad: I explain why I use the local struct in C++11 code just below the code. Basically, a lambda type doesn't define `result_type` for whatever reason. :( And in C++03, you can always go the not-nice route of defining it as a non-local struct. – Xeo Sep 20 '12 at 11:59
  • Duh. I had tacitly assumed that lambdas defined that but to be fair there’s no reason to – we have `result_of`, after all. Will Boost.Iterator add support for that? – Konrad Rudolph Sep 20 '12 at 12:15
  • @Konrad: One can only hope. :/ In other news, updated the code to show better / more concise ways for the specific case of a sequence. – Xeo Sep 20 '12 at 12:18
  • You answer is the closest one to meeting my expectations. Thanks! – Roman Shapovalov Sep 21 '12 at 16:49
6

The world is too large for C++ to ship a solution for everything. However, C++ doesn't want to be a huge supermarket full of ready meals for every conceivable palate. Rather, it is a small, well-equipped kitchen in which you, the C++ Master Chef, can cook up any solution you desire.

Here's a silly and very crude example of a sequence generator:

#include <iterator>

struct sequence_iterator : std::iterator<std::input_iterator_tag, int>
{
    sequence_iterator() : singular(true) { }
    sequence_iterator(int a, int b) : singular(false) start(a), end(b) { }
    bool singular;
    int start;
    int end;

    int operator*() { return start; }
    void operator++() { ++start; }

    bool operator==(sequence_iterator const & rhs) const
    {
        return (start == end) == rhs.singular;
    }
    bool operator!=(sequence_iterator const & rhs) const
    {
        return !operator==(rhs);
    }
};

Now you can say:

std::vector<int> v(sequence_iterator(1,10), sequence_iterator());

In the same vein, you can write a more general gadget that "calls a given functor a given number of times", etc. (e.g. take a function object by templated copy, and use the counters as repetition counters; and dereferencing calls the functor).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    Sbouldn't the `singular` on the non-default constructor be explicitly initialized to false? – Puppy Sep 20 '12 at 11:54
  • Why not simply inherit from `std::iterator` for the typedefs? :) – Xeo Sep 20 '12 at 11:58
  • I agree with you that sequence generation may be too specific to be included into C++. But my question is more general: I ask about any iterative function calls, e.g. RNG initialization. In fact, the point is to merge vector constructor and generate_n to make it more elegant. – Roman Shapovalov Sep 20 '12 at 11:58
  • @Xeo: Because I couldn't figure out how! :-) [Edit:] Done, thanks! – Kerrek SB Sep 20 '12 at 12:01
  • 1
    @overrider: yeah, a "generate_n"-iterator would be a good thing... so would a getline-iterator. Well, maybe one day. But for now, you know how to make your own! – Kerrek SB Sep 20 '12 at 12:04
  • @Kerrek: `boost::function_input_iterator`... *whistle* ♫~. Sadly, Boost.Iterator doesn't support Boost.ResultOf (and/or `decltype`) (yet). :( – Xeo Sep 20 '12 at 12:20
  • @Xeo: Yeah, Boost is an obvious source of iterators... NVidia's Thrust library also as a couple of "container-less" iterators. I have myself argued for using a [custom iterator](http://stackoverflow.com/a/9963438/596781) rather than non-standard `for`-loop conditions at times... I guess the more specific your needs get, the more difficult it becomes to have an all-purpose solution that would be worth including in the standard library, but Boost is definitely a good start. – Kerrek SB Sep 20 '12 at 12:28
2

If you're using a compiler that supports lambdas as you use in your question, then chances are pretty good it also includes std::iota, which at least makes the counting case a little cleaner:

std::vector <int> vec(10);
std::iota(begin(vec), end(vec), 0);

For this scenario (and quite a few others, I think) we'd really prefer an iota_n though:

namespace stdx {
template <class FwdIt, class T>
void iota_n(FwdIt b, size_t count, T val = T()) {
    for ( ; count; --count, ++b, ++val)
        *b = val;
}
}

Which, for your case you'd use like:

std::vector<int> vec;

stdx::iota_n(std::back_inserter(vec), 10);

As to why this wasn't included in the standard library, I really can't even guess. I suppose this could be seen as an argument in favor of ranges, so the algorithm would take a range, and we'd have an easy way to create a range from either a begin/end pair or a begin/count pair. I'm not sure I completely agree with that though -- ranges do seem to work well in some cases, but in others they make little or no sense. I'm not sure that without more work, we have an answer that's really a lot better than a pair of iterators.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
2

Nobody mentioned boost::assign, so I will introduce it here:

Example

#include <iostream>
#include <vector>
#include <boost/assign/std/vector.hpp> 
#include <cstdlib>

int main()
{
    std::vector<int> v1;
    std::vector<int> v2;
    boost::assign::push_back(v1).repeat_fun(9, &rand);
    int cnt = 0;
    boost::assign::push_back(v2).repeat_fun(10, [&cnt]()->int { return cnt++;});
    for (auto i : v1)
    {
        std::cout << i << ' ';
    }
    std::cout << std::endl;
    for (auto i : v2)
    {
        std::cout << i << ' ';
    }
}

Output

41 18467 6334 26500 19169 15724 11478 29358 26962
0 1 2 3 4 5 6 7 8 9

Jesse Good
  • 50,901
  • 14
  • 124
  • 166
0

You can use SFINAE to form a table :

#include <iostream>
#include <vector>

template <int n> struct coeff    { static int const value = coeff<n-1>::value + 3; };
template <>      struct coeff<0> { static int const value = 0; };

template<int... values> struct c1 {static int const value[sizeof...(values)];};
template<int... values> int const c1<values...>::value[] = {values...};

template<int n, int... values> struct c2 : c2< n-1, coeff<n-1>::value, values...> {};
template<int... values> struct c2< 0, values... > : c1<values...> {};

template<int n> struct table : c2< n > {
    static std::vector< unsigned int > FormTable()
    {
        return std::vector< unsigned int >( & c2< n >::value[0], & c2< n >::value[n] );
    }
};

int main()
{
    const auto myTable = table< 20 >::FormTable();

    for ( const auto & it : myTable )
    {
        std::cout<< it << std::endl;
    }
}
BЈовић
  • 62,405
  • 41
  • 173
  • 273