iota_n(STL中缺少的算法)的一个好的实现

What would be a good implementation of iota_n (missing algorithm from the STL)

本文关键字:一个 实现 算法 STL iota      更新时间:2023-10-16

有了C++11,STL现在有了std::iota函数(请参阅参考资料)。然而,与std::fill_nstd::generate_n相比,没有std::iota_n。什么是一个好的实施?直接循环(备选方案1)或用简单lambda表达式委派给std::generate_n(备选方案2)?

备选方案1)

template<class OutputIterator, class Size, class T>
OutputIterator iota_n(OutputIterator first, Size n, T value)
{
        while (n--)
                *first++ = value++;
        return first;
}

备选方案2)

template<class OutputIterator, class Size, class T>
OutputIterator iota_n(OutputIterator first, Size n, T value)
{
        return std::generate_n(first, n, [&](){ return value++; });
}    

这两种选择都会通过优化编译器生成等效的代码吗?

UPDATE:结合了@Marc Mutz的优点,也在其目的点返回迭代器。这也是std::generate_n在C++11中与C++98相比的更新方式。

作为一个随机示例,我使用g++ -S -O2 -masm=intel(GCC 4.7.1,x86_32)编译了以下代码:

void fill_it_up(int n, int * p, int val)
{
    asm volatile("DEBUG1");
    iota_n(p, n, val);
    asm volatile("DEBUG2");
    iota_m(p, n, val);
    asm volatile("DEBUG3");
    for (int i = 0; i != n; ++i) { *p++ = val++; }
    asm volatile("DEBUG4");
}

这里iota_n是第一个版本,iota_m是第二个版本。组件在所有三种情况下都是这样的:

    test    edi, edi
    jle .L4
    mov edx, eax
    neg edx
    lea ebx, [esi+edx*4]
    mov edx, eax
    lea ebp, [edi+eax]
    .p2align 4,,7
    .p2align 3
.L9:
    lea ecx, [edx+1]
    cmp ecx, ebp
    mov DWORD PTR [ebx-4+ecx*4], edx
    mov edx, ecx
    jne .L9

对于-O3,这三个版本也非常相似,但很多更长(使用条件移动和punpcklqdq等)。

您过于专注于代码生成,以至于忘记了正确处理接口。

您正确地要求OutputIterator,但如果您想再次调用它,会发生什么?

list<double> list(2 * N);
iota_n(list.begin(), N, 0);
// umm...
iota_n(list.begin() + N, N, 0); // doesn't compile!
iota_n(list.rbegin(), N, 0); // works, but create 0..N,N-1..0, not 0..N,0..N
auto it = list.begin();
std::advance(it, N);
iota_n(it, N, 0); // works, but ... yuck and ... slow (O(N))

iota_n中,您仍然知道自己在哪里,但您已经丢弃了这些信息,因此调用者无法在恒定时间内获取这些信息。

一般原则:不要丢弃有用的信息。

template <typename OutputIterator, typename SizeType, typename ValueType>
auto iota_n(OutputIterator dest, SizeType N, ValueType value) {
    while (N) {
        *dest = value;
        ++dest;
        ++value;
        --N;
    }
    // now, what do we know that the caller might not know?
    // N? No, it's zero.
    // value? Maybe, but it's just his value + his N
    // dest? Definitely. Caller cannot easily compute his dest + his N (O(N))
    //       So, return it:
    return dest;
}

有了这个定义,上面的例子就变得简单了:

list<double> list(2 * N);
auto it = iota_n(list.begin(), N, 0);
auto end = iota_n(it, N, 0);
assert(end == list.end());

一个假设的iota_n

std::iota_n(first, count, value)

可以用一个衬垫代替。

std::generate_n(first, count, [v=value]()mutable{return v++;})

我更喜欢这个有一个标准中没有的延迟函数。话虽如此,我认为std::iota_n应该在标准中。

可能将back_insertor与std::generate_n一起使用,这样可以避免集合的预分配。

vector<int> v3;
generate_n(back_inserter(v3),10,[i=1]() mutable{
    return i++;
});

我们可以将其交换为iota_n,直到我们得到iota_n:)