تنفيذ عامل التشغيل في C ++

مرحبًا اليوم أتمنى أن أريكم بعض السحر. هوايتي هي اختراع جميع أنواع القطع التي تبدو مستحيلة في C ++ ، مما يساعدني في تعلم جميع أنواع الخفايا للغة ، أو لمجرد الاستمتاع. العامل in بعدة لغات ، على سبيل المثال Python ، JS. لكنهم لم يأتوا بها في C ++ ، لكن في بعض الأحيان أريدها ، فلماذا لا تنفذها.

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"; } 


أعتقد أن الطريقة التي يجب أن يعمل بها المشغل واضحة. يأخذ الشيء الأيسر ويتحقق من وجود هذا الشيء في الكائن المشار إليه على اليمين ، والذي لا يجب أن يكون مجموعة. لا يوجد حل عالمي في حد ذاته ، تمامًا كما لا يوجد حل عالمي للمشغلين الآخرين ، لذلك ، تم اختراع إمكانية التحميل الزائد عليهم. لذلك ، بالنسبة إلى عامل التشغيل ، تحتاج إلى تنفيذ آلية مماثلة.

سيبدو الزائد مثل هذا.

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

أعتقد أن الفكرة واضحة ، والتعبير عن الأنواع.

  "some string" in some_map 

يجب أن يتحول إلى استدعاء دالة.

  operator_in("some string", some_map) 

يعد تنفيذ هذه الآلية أمرًا بسيطًا للغاية ، باستخدام الإمكانات الموجودة للتحميل الزائد للمشغل. العامل في حد ذاته هو في الأساس ماكرو يقوم بعملية الضرب.

  #define in *OP_IN_HELP{}* 

في هذه الحالة ، OP_IN_HELP عبارة عن فصل فارغ ولا يخدمنا إلا في تحديد الحمل الزائد الصحيح.

  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); } 

عامل التشغيل عبارة عن لوحة مرجعية ، مما يسمح لك بقبول أي نوع كوسيطة أولى. الآن نحن بحاجة إلى الحصول على الكائن الصحيح بطريقة أو بأخرى ، دون فقدان الجسم الأيسر. للقيام بذلك ، نقوم بتطبيق الفئة OP_IN_LVAL التي ستخزن كائننا الأيسر.

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

نظرًا لأن الكائن نفسه سيكون حيًا أثناء تشغيل التعبير ، فلا يوجد ما يدعو للقلق إذا احتفظنا بإشارة ثابتة إلى هذا الكائن. الآن كل ما تبقى لنا هو تنفيذ عامل التشغيل الداخلي للضرب ، والذي سيعود إلينا نتيجة عامل التشغيل الزائد ، فسيكون نموذجًا في حد ذاته.

  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); } }; 

في الواقع ، سيعمل هذا الحل بالفعل ، ولكنه محدود ولن يسمح لنا بالكتابة بهذه الطريقة.

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

من أجل أن نحصل على مثل هذه الفرصة ، نحتاج إلى التخلص من القيمة المرجعة لعامل التحميل الزائد. هناك إصداران لكيفية القيام بذلك ، يستخدم أحدهما قدرات C ++ 14 ، والآخر يعمل ضمن 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); } }; 

نظرًا لأنني أعمل بشكل أساسي في Visual Studio 2013 ، فأنا مقتصر على إطار C ++ 11 وسيعمل الحل ضمن C ++ 11 بنجاح في C ++ 14 ، لذا أنصحك باختياره.

مثال على تنفيذ عام في عامل التشغيل لـ 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); } 

تتيح لك فئة OpInResult تجاوز عامل الصب ، مما يسمح لنا باستخدامها في حالة. كما أنه يلغي عامل السهم ، الذي يسمح لك بإخفاء نفسك كمكرر يعيد unordered_map.find ().

يمكن العثور على مثال هنا cpp.sh/7rfdw

أود أيضًا أن أقول عن بعض ميزات هذا الحل.
يقوم Visual Studio بإنشاء القالب في مكان الاستخدام ، مما يعني أنه يجب الإعلان عن وظيفة التحميل الزائد نفسها قبل استخدام عامل التشغيل ، ولكن يمكن الإعلان عنها بعد تعريف فئة OP_IN_LVAL . يقوم GCC بدوره بإنشاء القالب في موقع الإعلان (عندما يواجه الاستخدام بمفرده) ، مما يعني أنه يجب التصريح ببيان زائد الحمل قبل الإعلان عن فئة OP_IN_LVAL . إذا لم يكن الأمر واضحًا تمامًا حول هذا الموضوع ، فإليك مثال. cpp.sh/5jxcq في هذا الكود ، قمت بزيادة التحميل على عامل التشغيل الموجود أسفل تعريف فئة OP_IN_LVAL وتوقف عن التحويل البرمجي في دول مجلس التعاون الخليجي (ما لم يتم تجميعه بعلامة -fpermissive) ، ولكنه يتم تجميعه بنجاح في Visual Studio.

في C ++ 17 ، أصبح من الممكن الكتابة هكذا.

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

ولكن يبدو لي تصميم المنظر

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

تبدو أجمل.

يمكن رؤية المزيد من الأمثلة على الأحمال الزائدة هنا github.com/ChaosOptima/operator_in

استنادًا إلى مبدأ التنفيذ الخاص بهذا المشغل ، لن تكون هناك أيضًا مشكلة في التنفيذ
وغيرها من العوامل والتعبيرات ، على سبيل المثال.

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

ملاحظة
أود أن أعرف إذا كنت مهتمًا بمثل هذه المواضيع ، فهل هناك أي نقطة في الكتابة حول هذا الموضوع هنا؟ وهل ترغب في معرفة كيفية تنفيذ أشياء أخرى مثيرة للاهتمام؟

عامل شرطي فارغ

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

مطابقة الأبوة

  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"; }; 

تعداد السلسلة

  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/ar419579/


All Articles