Bagaimana cara menerima iterator generik?

Saya ingin konstruktor menerima iterator apa pun yang memiliki sifat x dan kelas referensi y.

Class(std::iterator<std::random_access_iterator_tag, MyClass*> it);

Tetapi ketika saya mencoba melewati iterator tersebut, kompilasi gagal dengan Candidate constructor not viable: no known conversion from 'iterator' (aka '__deque_iterator<value_type, pointer, reference, __map_pointer, difference_type, __block_size>') to 'std::iterator<std::random_access_iterator_tag, MyClass *>'.

Kode penyisipan:

std::deque<MyClass*> collection_with_random_access_iterator{};
Class tmp(collection_with_random_access_iterator.begin());

Apa yang bisa saya lakukan di sini?


person Appleshell    schedule 07.09.2013    source sumber
comment
Perlu dicatat bahwa Anda mendapatkan kesalahan karena (setidaknya pada implementasi Anda) deque<T>::iterator tidak mewarisi dari iterator<random_access_iterator_tag, T> (Standar tidak mengharuskannya melakukan hal itu). (Ngomong-ngomong, MyClass** juga merupakan sejenis iterator akses acak ke MyClass* (dan misalnya vector<MyClass*>::iterator bisa saja seperti itu), tetapi petunjuk mentah tidak dapat diturunkan dari apa pun.)   -  person gx_    schedule 07.09.2013
comment
Sementara kita membahasnya.. std::iterator bukan polimorfik, itu hanya kelas dasar pembantu yang disediakan untuk menyederhanakan definisi iterator. Seharusnya tidak digunakan seperti itu.   -  person sbabbi    schedule 07.09.2013
comment
@sbabbi Benar, terima kasih sudah menunjukkannya. Mewarisi struct std::iterator dari kenyamanan, meskipun secara publik, hanyalah detail implementasi.   -  person gx_    schedule 07.09.2013
comment
@gx_ Sayang sekali standar c++ tidak mengharuskan iterators koleksi standar diturunkan dari induk yang sama.   -  person Appleshell    schedule 07.09.2013
comment
@AdamS Pointer memenuhi syarat sebagai iterator akses acak, dan vector<T>::iterator dan basic_string<T>::iterator dapat berupa T *. Bagaimanapun, warisan tidak akan relevan; Anda tidak ingin menggunakan fungsi virtual untuk melakukan dereferensi iterator.   -  person Potatoswatter    schedule 08.09.2013


Jawaban (2)


Iterator biasanya diterima berdasarkan nilai, kemudian didiskriminasi dengan delegasi ke fungsi lain.

Misalnya,

    template< typename iterator >
    Class( iterator it ) {
        init_class( * it, typename std::iterator_traits< iterator >::category() );
    }

    template< typename iterator >
    void init_class( iterator it, std::random_access_iterator_tag ) {
        for ( int i = 0; i != 42; i +=3 ) {
            do_something( it[ i ] );
        }
    }

    void do_something( MyClass * ) { … }
};

Melewati iterator yang salah akan mengakibatkan kesalahan di dalam fungsi, yang mungkin tidak jelas bagi pengguna. Tapi itulah yang biasanya terjadi dalam implementasi perpustakaan standar, dan itulah cara iterator awalnya digunakan sebelum SFINAE ditemukan. Jika ada kesalahan umum pengguna, Anda dapat menjebaknya secara spesifik dan mengarahkan pengguna ke kesalahan/komentar tertentu.

Jika tidak perlu memilih perilaku yang berbeda, tetapi ingin memastikan pengguna meneruskan iterator akses acak ke MyClass *, gunakan beberapa static_asserts dengan kondisi std::is_same sebagai jawaban sbabbi. Pengalaman pengguna yang dihasilkan lebih baik daripada SFINAE murni karena pesan kesalahannya mengatakan "Harap berikan iterator akses acak", bukan "Tidak ditemukan kelebihan beban".

person Potatoswatter    schedule 07.09.2013
comment
Perlu diperhatikan bahwa pengiriman tag dan/atau static_assert tidak selalu menggantikan SFINAE, terutama terkadang untuk konstruktor dan interaksi dengan sifat seperti is_constructible. Lihat flamingdangerzone.com/cxx11/2013 /02/11/ - person gx_; 07.09.2013
comment
@gx_ Alat yang tepat untuk pekerjaan itu. Apakah ada yang salah dengan jawaban ini atau Anda kehabisan suara positif untuk hari ini? ;v) - person Potatoswatter; 07.09.2013
comment
Tidak ada yang salah, saya hanya tidak berpikir untuk memilih. Telah diperbaiki :) (sepertinya Anda tidak memerlukan lebih banyak perwakilan, tetapi memang benar bahwa pemungutan suara itu penting) - person gx_; 07.09.2013

Bagaimana dengan:

template<class Iterator>
Class(Iterator it,
  typename std::enable_if<
          std::is_same<
                  typename std::iterator_traits<Iterator>::value_type,
                  MyClass*
          >::value //checks for value_type
          &&
          std::is_base_of<
                  std::random_access_iterator_tag,
                  typename std::iterator_traits<Iterator>::iterator_category
          >::value //checks for iterator category
    >::type * = 0);

EDIT Anda juga harus mempertimbangkan untuk mengganti std::is_same pertama dengan std::is_convertible, dan memeriksa const MyClass* jika Anda tidak akan mengubah input.

person sbabbi    schedule 07.09.2013
comment
Terima kasih, ini sepertinya sebuah pilihan, tetapi juga cukup besar. Saya lebih suka solusi yang lebih ringkas, tetapi saya akan mengingatnya dan kembali lagi jika tidak ada. - person Appleshell; 07.09.2013
comment
@AdamS: Alternatifnya adalah menerima tipe apa pun, mungkin memanggil parameter templat seperti RandomIterator untuk mendokumentasikan persyaratan informalnya. Itu membuat kodenya lebih sederhana, tetapi Anda akan mendapatkan pesan kesalahan yang lebih buruk jika jenisnya tidak memenuhi persyaratan. Alternatifnya, tunggu sampai konsep ditambahkan ke bahasa tersebut, semoga tahun depan. - person Mike Seymour; 07.09.2013
comment
Anda dapat membuat enable_if lebih sedikit bertele-tele: flamingdangerzone.com/cxx11/2012 /06/01/almost-static-if.html (sumber: stackoverflow.com/a/14623831 ) - person gx_; 07.09.2013
comment
@Mike Seymour Itu sebenarnya bisa menjadi pasangan yang baik dengan static_assert (dengan asumsi ini adalah satu-satunya konstruktor templat di kelas). - person sbabbi; 07.09.2013