2012-08-01 71 views
9

使用C++ 11,STL現在具有std::iota函數(請參閱reference)。然而,與std::fill_n,std::generate_n相反,沒有std::iota_n。這將是一個很好的實施?使用簡單的lambda表達式(備選方案2)直接循環(備選方案1)或委託給std::generate_n什麼是iota_n的好實現(缺少STL算法)

備選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的優點,也可以在目標點返回迭代器。與C++ 98相比,這也是如何在C++ 11中更新std::generate_n

+2

我認爲這個問題的重點是太具體的東西更普遍的東西:不同的循環結構。 – 2012-08-01 21:04:08

+2

爲什麼不嘗試它並比較彙編器? – 2012-08-01 21:04:42

+1

@KerrekSB沒有那麼多的grokking彙編輸出專家。我有興趣從具有這種專業知識的人那裏聽到,如果STL帶lambda的帶子通常會優化爲直線圈。如果是這樣的話,這將是一個更大的動機來寫更多的STL算法的變體,而不是思考錯綜複雜的循環。 – TemplateRex 2012-08-01 21:07:15

回答

10

作爲隨機例如,我編譯下面的代碼與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以及諸如此類)。

+1

謝謝,這是一個很好的答案。不管'punpcklqdq'是幹什麼的,(我在MSDN上查過),可以肯定的是,知道調用'std :: generate_n' + lambda幾乎沒有任何抽象懲罰。 – TemplateRex 2012-08-01 21:37:00

3

你很專注於代碼生成,你忘記了正確的界面。

你正確地要求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()); 
+1

不錯的一點,我同意現有的'iota'和假定的'iota_n'應該返回目的地。 – TemplateRex 2015-03-09 17:45:47

+1

@TemplateRex:'iota'不返回目的地,因爲它等於它的第二個參數。但'iota_n'不同,終點是隱含的,因此'iota_n'確實需要返回目的地。 – 2015-03-10 16:29:19

+1

啊謝謝,現在我明白了。我用你的信息更新了答案。請注意'std :: generate_n'也在C++ 11中得到了這個升級。 – TemplateRex 2015-03-10 19:20:41