Pertanyaan Dapatkah jangkauan berbasis C ++ 11 untuk melakukan / memeriksa operasi / kondisi ekstra?


Saya menemukan lingkaran berbasis C ++ 11 dan sudah menyukainya. Itu membuat Anda menghemat banyak waktu saat coding.

Namun, saya sudah terbiasa menulis beberapa loop dengan pernyataan / ketentuan tambahan dan saya bertanya-tanya apakah ini dapat dicapai saat menggunakan loop berbasis rentang C ++ 11:

1. Penambahan ekstra

std::vector<int> v = { 1, 2, 3, 4, 5 };
size_t index = 0;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter, ++index )
{
    std::cout << "v at index " << index << " is " << *iter;
}

Bisa menjadi:

size_t index = 0;
for ( int val : v )
{
    std::cout << "v at index " << index << " is " << *iter;
    ++index;
}

Namun, incrementing index dalam for lingkaran lebih baik karena dijamin (bertambah bahkan jika for lingkaran memiliki continue pernyataan misalnya)

Apakah ada cara untuk bergerak ++index di dalam for pernyataan?

2. Dapatkan indeks iterasi secara dinamis

std::vector<int> v = { 1, 2, 3, 4, 5 };
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter )
{
    std::cout << "v at index " << ( iter - v.begin() ) << " is " << *iter;
}

Dapatkah sesuatu yang serupa dapat dicapai dengan loop berbasis rentang C ++ 11? Apakah ada cara untuk mengetahui berapa banyak iterasi yang dilakukan selama ini?

3. Kondisi keluar tambahan

Saya sering menggunakan ini dalam kode di mana istirahat dilarang sebagai panduan pengkodean:

std::vector<int> v = { 1, 2, 3, 4, 5 };
bool continueLoop = true;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end() && continueLoop; ++iter )
{
    std::cout << "v value is " << *iter;
    if ( *iter == 4 )
        continueLoop = false;
}

Dapatkah sesuatu yang serupa dapat dicapai dengan C ++ 11 range-based loop (break exeuction tanpa menggunakan break)?


32
2018-04-14 07:04


asal


Jawaban:


Sayangnya, Anda tidak dapat memasukkan kenaikan ke dalam rentang berdasarkan pengulangan. Namun, dalam kasus spesifik Anda - sebagai std::vector menyimpan elemen-elemennya secara kontroversial dalam memori - Anda dapat mensimulasikan opsi 2 dengan kembali ke pointer (terima kasih kepada @ M.M dan @ Jarod42 untuk koreksi dan peningkatan):

for ( const int& val : v )  {
    std::cout << "v at index " << &val-v.data() << " is " << val; 
}

lebih umum:

for ( const auto& val : v )  {
    std::cout << "v at index " << std::addressof(val)-v.data() << " is " << val; 
}

Hal lain yang dapat Anda lakukan adalah menulis a index_range kelas, yang mewakili koleksi indeks yang dapat Anda ulas dalam rentang Anda berdasarkan loop:

struct index_range_it {
    size_t idx;
    size_t operator*(){
        return idx;
    }
    index_range_it& operator++() {
        idx++;
        return (*this);
    }
};

bool operator!=(index_range_it l,index_range_it r) {
    return l.idx != r.idx;
}

struct index_range {
    size_t size;
    index_range_it end(){return index_range_it{size};}
    index_range_it begin(){return index_range_it{0};}
};

int main()
{
    for (auto i: index_range{v.size()}){
        std::cout << "v at index " << i << " is " << v[i]; 
    }        
}

Implementasi penuh gagasan ini dapat ditemukan misalnya, sini

Rentang demikian dapat juga disusun menjadi sesuatu, di mana iterator mengembalikan objek proxy yang berisi indeks serta referensi ke objek saat ini dan dengan pengikatan terstruktur c ++ 17 yang akan lebih mudah digunakan.


14
2018-04-14 07:16



Melihat range-v3 dan cppitertools.

cppitertools menyediakan sangat nyaman enumerate:

std::vector<int> v = { 1, 2, 3, 4, 5 };
for (auto&& e : enumerate(v))
{
    std::cout << "v at index " << e.index << " is " << e.element;
}

Range-v3 sayangnya tidak memiliki enumerate, yang membuat saya sangat sedih, tetapi Anda dapat menulis menggunakan Anda sendiri view::ints dan view::zip*. Range-v3 memiliki keuntungan besar bahwa itu adalah dasar untuk rentang yang diusulkan untuk pustaka standar. Komposisi kisaran memungkinkan untuk membangun abstraksi bersih.

Mengenai contoh terakhir Anda, saya berpendapat bahwa Anda harus menghindari loop sama sekali jika Anda perlu mengurangi kompleksitas. Alih-alih menggunakan algoritma yang tepat seperti std::find_if, std::any_of yang cocok dengan tugas Anda tanpa harus mengekspresikan aliran kontrol.


13
2018-04-14 10:41



Untuk penampung umum, Anda tidak bisa mendapatkan indeks atau iterator dari loop berbasis jangkauan. Sebaliknya Anda juga harus menyimpan variabel terpisah, atau kembali ke loop iterator.

Tampilan iterator dapat ditulis sedikit lebih sederhana sejak C ++ 11:

for( auto iter = begin(v); iter != end(v); ++iter )

Untuk kasus spesifik dari vektor yang dapat Anda lakukan:

for ( auto& val : v )
{
    cout << "Index is " << (&val - &v[0]) << '\n';
}

yang berfungsi karena vektor menggunakan penyimpanan bersebelahan.


6
2018-04-14 07:26



Ini sedikit hal yang bisa dilakukan # 2

#include <iterator>
#include <utility>
#include <type_traits>
#include <cstddef>

template<typename Range>
class RangeBasedAdaptor
{
    Range& range;
public:
    RangeBasedAdaptor(Range& r) : range(r) {}
    struct iterator;
    typedef typename std::remove_reference<decltype(*std::begin(range))>::type mapped_type;
    typedef decltype(std::begin(range)) underlying_iterator;

    struct value_type
    {
        std::size_t index() const { return idx; }
        mapped_type& value() { return *ui; }
        const mapped_type& value() const { return *ui; }
    private:
        std::size_t idx;
        underlying_iterator ui;
    friend
        struct iterator;
    };

    struct iterator
    {
        iterator();
        iterator& operator++() { ++val.ui; ++val.idx; return *this; }
        value_type& operator*() { return val; }
        bool operator!=(iterator other) { return val.ui != other.val.ui; }
    private:
        iterator( underlying_iterator ui, std::size_t idx ) { val.idx=idx; val.ui=ui; }
        value_type val;
    friend
        class RangeBasedAdaptor;
    };

    iterator begin() { return iterator{ std::begin(range), 0 }; }
    iterator end() { return iterator{ std::end(range), (std::size_t)-1 }; }
};

template<typename Range>
auto indexed(Range& r) -> RangeBasedAdaptor<Range>
{
    return {r};
}

// -------------------------------------------------------------------------------------

#include <iostream>
#include <vector>
#include <list>

int main()
{
    std::vector<int> foo = { 1,2,3,4,5,6 };

    for( auto& val : indexed(foo) )
    {
        val.value() += 3;
        std::cout << val.index() << " : " << val.value() << std::endl;
    }

    const std::list<float> foo2 = { 1.1f, 2.2f, 3.3f };

    for( auto& val : indexed(foo2) )
    {
        std::cout << val.index() << " : " << val.value() << std::endl;
    }
}

Ini hanya dimaksudkan dengan rentang berdasarkan untuk loop, maka iterasi minimal.


4
2018-04-14 09:56



Dalam bahasa komputer, secara tradisional loop "for" adalah loop dengan kondisi perulangan yang ditentukan bahasa. Jika programmer ingin menentukan kondisi looping mereka sendiri, mereka menggunakan loop "while". Dari perspektif ini, C ++ berbasis-rangkaian untuk loop adalah pertama kalinya bahasa benar-benar memiliki konstruksi loop "untuk" yang nyata. Jadi mungkin diperlukan sedikit programmer C untuk membungkus pikiran mereka di sekitar fakta bahwa jika mereka tidak dapat menangani kondisi loop yang dihasilkan oleh compiler, mereka seharusnya menggunakan konstruk yang berbeda.

Itu dikatakan, karena iterator bisa menjadi objek khusus, Anda dapat melakukan apa pun yang Anda inginkan dengan loop berbasis rentang dengan menulis sendiri iterator khusus. Di masa lalu, saya biasanya menemukan upaya ini tidak sepadan dengan kode tambahan, kecuali Anda akan menggunakan kembali iterator itu beberapa kali.

1. Penambahan ekstra

Namun, kenaikan indeks dalam for loop lebih baik karena   dijamin (bertambah bahkan jika untuk loop telah melanjutkan pernyataan untuk   contoh)

Apakah ada cara untuk memindahkan indeks ++ di dalam pernyataan?

Ya, dengan iterator khusus. Namun, itu banyak pekerjaan. Ini mudah:

for (auto element : container) {
   ++index;
}

Di sini kita juga tahu bahwa jaminannya akan bertambah, karena ditempatkan di bagian atas sebelum kemungkinan istirahat atau melanjutkan pernyataan.

  1. Dapatkan indeks iterasi secara dinamis

Dapatkah sesuatu yang serupa dapat dicapai dengan loop berbasis rentang C ++ 11? Aku s   ada cara untuk mengetahui berapa banyak iterasi yang dilakukan sejauh ini?

Sekali lagi, ini bisa dilakukan dengan iterator khusus, tetapi hampir pasti tidak sepadan. Saya harus melakukan ini sendiri minggu lalu, dan solusinya tampak sangat mirip dengan kode di # 1 di atas.

  1. Kondisi keluar tambahan

Saya sering menggunakan ini dalam kode di mana istirahat dilarang sebagai pengkodean   guidline:

Ini seharusnya tak pernah berada dalam pedoman pengkodean. Itu salah datar. Saya tidak berdebat untuk Anda melanggar pedoman Anda. Tapi saya berdebat untuk siapa pun yang membaca ini untuk tidak pernah memasukkan hal semacam itu ke dalam dokumen panduan pengkodean lagi.

Ada aturan umum untuk pengkodean terstruktur yang baik bahwa setiap blok kode hanya boleh memiliki satu titik keluar (alias: goto dianggap berbahaya). Namun, sebuah loop dengan dua pernyataan keluar masih hanya memiliki satu titik keluar. Keduanya keluar kontrol kembali ke titik yang sama di luar loop.

Secara lebih praktis, ada banyak jenis loop yang harus lebih rumit (misalnya: lebih sulit untuk dipahami dan tetap bekerja dengan baik) jika Anda tidak dapat melakukan tes jalan keluar di tengah-tengahnya. Jika suatu pedoman secara rutin memaksa Anda untuk menulis kode yang lebih tumpul, ini adalah pedoman yang buruk.

Sekali lagi, Anda bisa menyelesaikan ini dengan iterator kustom. Dalam hal ini, saya berpendapat itu mungkin cara untuk pergi. Tentu, itu ton lebih banyak kode dari nilainya hanya untuk bekerja di sekitar pedoman coding bodoh Anda. Tapi itu kesalahan pedomannya, bukan kesalahan Anda.


3
2018-04-14 15:16



Saya tidak akan menulis kode yang menggantikan yang sangat bagus break pernyataan.

Mendapatkan indeks untuk sebuah vektor (yang saat itu berguna) adalah mudah: iterator over auto& x:vlalu kurangi std::addressof(x)-v.data().

Yang daun # 1.

template<class It, class Operation>
struct iterator_with_extra_increment_t {
  using self=iterator_with_extra_increment_t;
  It it;
  Operation& op;
  void operator++(){ ++it; op(); }
  auto operator*()->decltype(*std::declval<It&>()) { return *it; }
  friend bool operator!=(self const& lhs, self const& rhs){
    return lhs.it != rhs.it;
  }
  friend bool operator==(self const& lhs, self const& rhs){
    return lhs.it == rhs.it;
  }
};
template<class It, class Operation>
iterator_with_extra_increment_t<It, Operation>
iterator_with_extra_increment( It it, Operation& operation ) {
  return {std::move(it), operation};
}
template<class Range, class Modify>
struct iterate_modified_t {
  Range r;
  Modify m;
  auto begin() { using std::begin; return m(begin(r)); }
  auto end() { using std::end; return m(end(r)); }
};
template<class Range, class Modify>
iterate_modified_t<Range, std::decay_t<Modify>>
iterate_modified( Range&& r, Modify&& m) {
  return {std::forward<Range>(r), std::forward<Modify>(m)};
}
template<class Range, class Op>
auto also_on_inc( Range&& r, Op&& op ) {
  auto modify = [op = std::forward<Op>(op)](auto&& it) {
    return iterator_with_extra_increment(decltype(it)(it), op);
  };
  return iterate_modified( std::forward<Range>(r), std::move(modify) );
}

sekarang kita punya also_on_inc:

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : also_on_inc(a, [&]{++count;}) ) {
  std::cout << count << "->" << x << '\n';
}

contoh hidup.

Beberapa kode di atas adalah C ++ 14 karena saya terlalu malas untuk menulis ->decltype klausa.

Kami dapat memperbaiki sintaks itu dengan penyalahgunaan operator ke sesuatu seperti:

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} ) {
  std::cout << count << "->" << x << '\n';
}    

jika kita gila, yang memungkinkan kita melakukannya

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} *also_on_inc* [&]{std::cout << count << '\n';} ) {
  std::cout << count << "->" << x << '\n';
}

perangkaian lebih mudah dari klausul tersebut.


0
2018-04-14 14:02