2012-07-04 96 views
21

可能重複:
Find position of element in C++11 range-based for loop?類Python循環枚舉

我有一個vector,我想重複它,並在同一時間,有機會獲得每個單獨元素的索引(我需​​要將元素及其索引傳遞給函數)。我考慮了以下兩種解決方案:

std::vector<int> v = { 10, 20, 30 }; 

// Solution 1 
for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx) 
    foo(v[idx], idx); 

// Solution 2 
for (auto it = v.begin(); it != v.end(); ++it) 
    foo(*it, it - v.begin()); 

我想知道是否有更緊湊的解決方案。類似於Python的enumerate。這是我使用C++ 11範圍循環所得到的最接近的結果,但是必須在私有範圍內定義循環外的索引,看起來好像是比1或2更糟糕的解決方案:

{ 
    int idx = 0; 
    for (auto& elem : v) 
     foo(elem, idx++); 
} 

有沒有什麼辦法(或許使用Boost)來簡化最新的例子,使索引得到獨立的循環?

+5

爲什麼簡化簡單的事情? :-) – Kos

+0

您將不得不創建一個類似生成器的函數/對象,它返回std :: pair並使用該對的第一個和第二個字段。你可以使用宏來實現這個技巧,但是在C++中使用類似Python的語法沒有方便和優雅的方法。你的第二個解決方案可能是最好的。 – Morwenn

+0

@Kos我對解決方案2非常好。只是好奇,如果有更簡單的方法:) – betabandido

回答

10

由於@Kos說,這是這樣一個簡單的事情,我真的不認爲有必要進一步簡化它,並會親自只是固守傳統的for循環與指標,但我會溝std::vector<T>::size_type和簡單的使用std::size_t

for(std::size_t i = 0; i < v.size(); ++i) 
    foo(v[i], i); 

我不是解決方案2.太熱衷它需要(有點隱藏)隨機訪問迭代器這不會讓你輕鬆交換容器,它是強者的一個迭代器點。如果你想使用迭代器,並使其通用(以及可能導致性能命中時,迭代器是隨機訪問),我推薦使用std::distance

for(auto it(v.begin()); it != v.end(); ++it) 
    foo(*it, std::distance(it, v.begin()); 
+3

鑑於任何嘗試接近Python枚舉的任何嘗試似乎最終都會導致龐大的代碼膨脹,我認爲最好只使用這兩種解決方案中的任何一種。 – betabandido

1

一種方法是將循環包裝在您自己的函數中。

#include <iostream> 
#include <vector> 
#include <string> 

template<typename T, typename F> 
void mapWithIndex(std::vector<T> vec, F fun) { 
    for(int i = 0; i < vec.size(); i++) 
     fun(vec[i], i); 
} 

int main() { 
    std::vector<std::string> vec = {"hello", "cup", "of", "tea"}; 
    mapWithIndex(vec, [](std::string s, int i){ 
     std::cout << i << " " << s << '\n'; 
    }); 
} 
+1

海事組織這隻會使事情進一步複雜... – SingerOfTheFall

+2

你做一個公平的觀點。通常情況下,一個簡單的for循環是最好的。顯然OP不想要一個。 –

+1

我其實想要進一步簡化代碼(當然,如果可能的話)。 '對於idx,列舉(v)中的elem:foo(idx,elem)'在我看來,比我在問題或答案中發佈的任何其他解決方案都更簡單。但是,當然,這是一個Python解決方案,我正在尋求一個C++解決方案。 – betabandido

13

下面是使用某種有趣的解決方案懶惰的評價。首先,構建發電機對象enumerate_object

template<typename Iterable> 
class enumerate_object 
{ 
    private: 
     Iterable _iter; 
     std::size_t _size; 
     decltype(std::begin(_iter)) _begin; 
     const decltype(std::end(_iter)) _end; 

    public: 
     enumerate_object(Iterable iter): 
      _iter(iter), 
      _size(0), 
      _begin(std::begin(iter)), 
      _end(std::end(iter)) 
     {} 

     const enumerate_object& begin() const { return *this; } 
     const enumerate_object& end() const { return *this; } 

     bool operator!=(const enumerate_object&) const 
     { 
      return _begin != _end; 
     } 

     void operator++() 
     { 
      ++_begin; 
      ++_size; 
     } 

     auto operator*() const 
      -> std::pair<std::size_t, decltype(*_begin)> 
     { 
      return { _size, *_begin }; 
     } 
}; 

然後,創建一個包裝函數枚舉會推斷出模板參數和返回發電機:

template<typename Iterable> 
auto enumerate(Iterable&& iter) 
    -> enumerate_object<Iterable> 
{ 
    return { std::forward<Iterable>(iter) }; 
} 

現在,您可以使用函數的方式:

int main() 
{ 
    std::vector<double> vec = { 1., 2., 3., 4., 5. }; 
    for (auto&& a: enumerate(vec)) { 
     size_t index = std::get<0>(a); 
     double& value = std::get<1>(a); 

     value += index; 
    } 
} 

以上的實施僅僅是一個玩具:它應與const和非const左值參考工作s以及右值引用,但是對於後者來說,它具有實際的代價,考慮到它複製整個可迭代對象多次。這個問題肯定可以通過額外的調整來解決。

由於C++ 17,分解聲明甚至允許你有涼爽的Python的語法來命名索引並直接在for初始值:

int main() 
{ 
    std::vector<double> vec = { 1., 2., 3., 4., 5. }; 
    for (auto&& [index, value] a: enumerate(vec)) { 
     value += index; 
    } 
} 

我沒有一個C++ 17兼容的編譯器來檢查它,但我希望分解中的auto&&能夠推斷indexstd::size_tvaluedouble&

+0

如果你通過一個臨時的'enumerate',你的代碼將會爆炸。 – Xeo

+0

您正在使用哪種編譯器?它不能用g ++ 4.6或4.7編譯。 – betabandido

+0

@Xeo這不好笑嗎?你可能是對的,我真的不知道如何製作更安全的版本。無論如何,使用該功能並不像普通的舊解決方案那樣方便。 – Morwenn