Implementasi operator dalam di C ++

Hai Hari ini saya berharap menunjukkan beberapa keajaiban. Hobi saya adalah menciptakan semua jenis karya yang tampaknya mustahil di C ++, yang membantu saya mempelajari semua jenis seluk-beluk bahasa, atau hanya untuk bersenang-senang. Operator in ada dalam beberapa bahasa, misalnya Python, JS. Tetapi mereka tidak membawanya dalam C ++, tetapi kadang-kadang saya menginginkannya, jadi mengapa tidak menerapkannya.

std::unordered_map<std::string, std::string> some_map = { { "black", "white" }, { "cat", "dog" }, { "day", "night" } }; if (auto res = "cat" in some_map) { res->second = "fish"; } 


Bagaimana operator seharusnya bekerja, saya pikir sudah jelas. Dia mengambil objek kiri dan memeriksa apakah ada kejadian objek ini di objek yang ditunjukkan di sebelah kanan, yang tidak harus menjadi koleksi. Tidak ada solusi universal dengan sendirinya, sama seperti tidak ada solusi universal untuk operator lain, oleh karena itu, kemungkinan overloading mereka diciptakan. Oleh karena itu, untuk operator dalam, Anda perlu menerapkan mekanisme serupa.

Kelebihan akan terlihat seperti ini.

 bool operator_in(const string& key, const unordered_map<string, string>& data) { return data.find(key) != data.end(); } 

Saya pikir idenya jelas, ekspresi spesies.

  "some string" in some_map 

Seharusnya berubah menjadi panggilan fungsi.

  operator_in("some string", some_map) 

Menerapkan mekanisme ini cukup sederhana, menggunakan kapabilitas yang ada untuk overloading operator. Operator in itu sendiri pada dasarnya adalah makro yang melakukan perkalian.

  #define in *OP_IN_HELP{}* 

Dalam hal ini, OP_IN_HELP adalah kelas kosong dan hanya melayani kami untuk memilih kelebihan beban yang benar.

  class OP_IN_HELP {}; template<class TIn> OP_IN_LVAL<TIn> operator*(const TIn& data, const OP_IN_HELP&) { return OP_IN_LVAL<TIn>(data); } 

Operatornya adalah boilerplate, yang memungkinkan Anda menerima jenis apa pun sebagai argumen pertama. Sekarang kita perlu entah bagaimana mendapatkan objek yang tepat, tanpa kehilangan yang kiri. Untuk melakukan ini, kami menerapkan kelas OP_IN_LVAL yang akan menyimpan objek kiri kami.

  template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) : m_in(val) {}; }; 

Karena objek itu sendiri akan hidup ketika ekspresi sedang berjalan, tidak ada yang perlu dikhawatirkan jika kita tetap menggunakan referensi konstan untuk objek ini. Sekarang yang tersisa bagi kita adalah mengimplementasikan operator internal dari perkalian, yang akan mengembalikan kepada kita hasil dari operator yang kelebihan beban, itu akan menjadi template dengan sendirinya.

  template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) : m_in(val) {}; template<class TWhat> bool operator*(const TWhat& what) const { return operator_in(m_in, what); } }; 

Sebenarnya solusi ini sudah berfungsi, tetapi terbatas dan tidak akan mengizinkan kami menulis dengan cara ini.

  if (auto res = "true" in some_map) { res->second = "false"; } 

Agar kita memiliki kesempatan seperti itu, kita perlu membuang nilai kembali dari operator yang kelebihan beban. Ada dua versi cara melakukan ini, yang satu menggunakan kemampuan C ++ 14, yang lainnya bekerja di dalam C ++ 11.

  template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) :m_in(val) {}; //   C++14 template<class TWhat> auto operator*(const TWhat& what) const { return operator_in(m_in, what); } //   C++11 template<class TWhat> auto operator*(const TWhat& what) const -> decltype(operator_in(m_in, what)) { return operator_in(m_in, what); } //       //       template<class TWhat> auto operator*(TWhat& what) const -> decltype(operator_in(m_in, what)) { return operator_in(m_in, what); } }; 

Karena saya terutama bekerja di Visual Studio 2013, saya terbatas pada kerangka kerja C ++ 11 dan solusi dalam C ++ 11 akan bekerja dengan sukses di C ++ 14, jadi saya menyarankan Anda untuk memilihnya.

Contoh implementasi dari operator generik dalam untuk unordered_map.

 template<class TIterator> class OpInResult { bool m_result; TIterator m_iter; public: OpInResult(bool result, TIterator& iter) : m_result(result), m_iter(iter) {} operator bool() { return m_result; } TIterator& operator->() { return m_iter; } TIterator& data() { return m_iter; } }; template<class TKey, class TVal> auto operator_in(const TKey& key, std::unordered_map<TKey, TVal>& data) -> OpInResult<typename std::unordered_map<TKey, TVal>::iterator> { auto iter = data.find(key); return OpInResult<typename std::unordered_map<TKey, TVal>::iterator>(iter != data.end(), iter); } template<class TKey, class TVal> auto operator_in(const char* key, std::unordered_map<TKey, TVal>& data) -> OpInResult<typename std::unordered_map<TKey, TVal>::iterator> { auto iter = data.find(key); return OpInResult<typename std::unordered_map<TKey, TVal>::iterator>(iter != data.end(), iter); } 

Kelas OpInResult memungkinkan Anda untuk menimpa operator transmisi, yang memungkinkan kami untuk menggunakannya jika. Itu juga menimpa operator panah, yang memungkinkan Anda untuk menutupi diri Anda sebagai iterator yang mengembalikan unordered_map.find ().

Contohnya dapat ditemukan di sini cpp.sh/7rfdw

Saya juga ingin mengatakan tentang beberapa fitur dari solusi ini.
Visual Studio instantiates templat di tempat penggunaan, yang berarti bahwa fungsi kelebihan itu sendiri harus dideklarasikan sebelum operator digunakan, tetapi dapat dideklarasikan setelah deklarasi kelas OP_IN_LVAL . GCC pada gilirannya membuat instantiates templat di lokasi pernyataan (ketika menemukan penggunaannya sendiri), yang berarti bahwa pernyataan kelebihan beban harus dideklarasikan sebelum kelas OP_IN_LVAL dideklarasikan . Jika tidak sepenuhnya jelas tentang apa ini, maka inilah contohnya. cpp.sh/5jxcq Dalam kode ini, saya hanya membebani operator di bawah deklarasi kelas OP_IN_LVAL dan berhenti mengkompilasi di GCC (kecuali jika dikompilasi dengan flag -fpermissive), tetapi berhasil dikompilasi di Visual Studio.

Di C ++ 17, menjadi mungkin untuk menulis seperti ini.

  if (auto res = some_map.find("true"); res != some_map.end()) { res->second = "false"; } 

Tapi menurut saya desain tampilan

  if (auto res = "true" in some_map) { res->second = "false"; } 

Terlihat lebih bagus.

Contoh lebih banyak kelebihan dapat dilihat di sini github.com/ChaosOptima/operator_in

Berdasarkan prinsip implementasi operator ini, juga tidak akan ada masalah untuk diterapkan
dan operator dan ekspresi lainnya, misalnya.

  negative = FROM some_vector WHERE [](int x){return x < 0;}; 

PS
Saya ingin tahu jika Anda tertarik dengan topik seperti itu, apakah ada gunanya menulis tentang hal ini di sini? Dan apakah Anda ingin tahu bagaimana menerapkan hal-hal menarik lainnya?

operator kondisi nol

  auto result = $if some_ptr $->func1()$->func2()$->func3(10, 11, 0)$endif; 

pencocokan patern

  succes = patern_match val with_type(int x) { cout << "some int " << x << '\n'; } with_cast(const std::vector<int>& items) { for (auto&& val : items) std::cout << val << ' '; std::cout << '\n'; } with(std::string()) [&](const std::string&) { cout << "empty string\n"; } with(oneof<std::string>("one", "two", "three")) [&](const std::string& value) { cout << value << "\n"; } with_cast(const std::string& str) { cout << "some str " << str << '\n'; } at_default { cout << "no match"; }; 

string enum

  StringEnum Power $def ( POW0, POW1, POW2 = POW1 * 2, POW3, POW4 = POW3 + 1, POW8 = POW4 * 2, POW9, POW10 ); to_string(Power::POW0) from_string<Power>("POW0") 

Source: https://habr.com/ru/post/id419579/


All Articles