malat
malat

Reputation: 12530

std::iota is very limited

Coming from a Python world, I find the function std::iota very limited. Why is the interface restricted to not take any UnaryFunction ?

For instance I can convert

>>> x = range(0, 10)

into

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

But how would one do:

>>> x = range(0,20,2)

or even

>>> x = range(10,0,-1)

I know this is trivial to write one such function or use Boost, but I figured that C++ committee must have picked this design with care. So clearly I am missing something from C++11.

Upvotes: 27

Views: 11138

Answers (4)

Peter - Reinstate Monica
Peter - Reinstate Monica

Reputation: 16122

With strides from C++23 you can write

iota(0) | stride(2)

instead of

iota(0) | filter([](auto i) { return i % 2 == 0; })

which feels much more clumsy and unnecessarily computing intensive (even if the compilers can perhaps produce something simpler from it).

Upvotes: 2

seleciii44
seleciii44

Reputation: 1569

how about std::generate?

int n = -2;
std::generate(x.begin(), x.end(), [&n]{ return n+=2; }); 
int n = 10;
std::generate(x.begin(), x.end(), [&n]{ return n--;});

Upvotes: 39

Alexey Sokolov
Alexey Sokolov

Reputation: 159

With C++20 ranges, you can write it like this:

static auto stepped_iota(int start, int step) {
  return std::ranges::views::iota(0) |
         std::ranges::views::transform([=](int x) { return x * step + start; });
}

void f() {
  for (int x : stepped_iota(0, 2)) { ... }
}

https://godbolt.org/z/3G49rs

Or, if you want the range to be finite:

static auto stepped_iota(int start, int end, int step) {
  return std::ranges::views::iota(0, (end - start + step - 1) / step) |
         std::ranges::views::transform([=](int x) { return x * step + start; });
}

Upvotes: 10

YSC
YSC

Reputation: 40170

But how would one do:

x = range(0,20,2)

Alternatively to std::generate() (see other answer), you can provide your own unary function to std::iota(), it just have to be called operator++():

#include <iostream>
#include <functional>
#include <numeric>
#include <vector>

template<class T>
struct IotaWrapper
{
    typedef T type;
    typedef std::function<type(const type&)> IncrFunction;

    type value;
    IncrFunction incrFunction;

    IotaWrapper() = delete;
    IotaWrapper(const type& n, const IncrFunction& incrFunction) : value(n), incrFunction(incrFunction) {};

    operator type() { return value; }
    IotaWrapper& operator++() { value = incrFunction(value); return *this; }
};

int main()
{
    IotaWrapper<int> n(0, [](const int& n){ return n+2; });
    std::vector<int> v(10);
    std::iota(v.begin(), v.end(), n);

    for (auto i : v)
        std::cout << i << ' ';
    std::cout << std::endl;
}

Output: 0 2 4 6 8 10 12 14 16 18

Demo


Here is an idea of how one could implement Range():

struct Range
{
    template<class Value, class Incr>
    std::vector<Value> operator()(const Value& first, const Value& last, const Incr& increment)
    {
        IotaWrapper<Value> iota(first, [=](const int& n){ return n+increment; });
        std::vector<Value> result((last - first) / increment);
        std::iota(result.begin(), result.end(), iota);
        return result;
    }
};

Demo

Upvotes: 16

Related Questions