العمل مع الأوتار في مرحلة التجميع في C ++ الحديثة


إذا كنت تبرمج في C ++ ، فربما تساءلت لماذا لا يمكنك مقارنة سلسلتين حرفيتين أو إجراء سلسلة بينهما:


auto str = "hello" + "world"; //   if ("hello" < "world") { // ,    ,   // ... } 

ومع ذلك ، كما يقولون ، "هذا مستحيل ، ولكن إذا كنت تريد ذلك حقًا ، يمكنك ذلك." سنكسر الصور النمطية تحت القطع ، وفي مرحلة التجميع.


لماذا كل هذا مطلوب


في أحد المشاريع التي كنت أعمل عليها ، كان من المعتاد استخدام std :: string كثوابت سلسلة. يحتوي المشروع على عدة وحدات تم فيها تحديد ثوابت السلسلة العالمية:


 // plugin.h const std::string PLUGIN_PATH = "/usr/local/lib/project/plugins/"; // ... 

 // sample_plugin.h const std::string SAMPLE_PLUGIN_LIB = PLUGIN_PATH + "sample.so"; // ... 

أعتقد أنك خمنت بالفعل ما حدث في يوم من الأيام. أخذت SAMPLE_PLUGIN_PATH القيمة "sample.so" ، على الرغم من حقيقة أن PLUGIN_PATH لها القيمة "/usr/local/lib/project/plugins/" ، كما هو متوقع. كيف يمكن أن يحدث هذا؟ كل شيء بسيط للغاية ، لم يتم تحديد ترتيب تهيئة الكائنات العامة ، في وقت التهيئة SAMPLE_PLUGIN_PATH المتغير PLUGIN_PATH فارغًا.


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


عندها كان لدي فكرة العمل مع الأوتار في مرحلة التجميع ، مما أدى في النهاية إلى كتابة هذه المقالة.


في هذه المقالة ، سننظر في السطور التي يمكن تشغيلها في مرحلة التجميع. سوف نسمي هذه الخطوط ثابتة.


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


مطلوب على الأقل C ++ 14 لاستخدام المكتبة.


تعريف السلسلة الثابتة


نحدد السلسلة الثابتة على أنها مجموعة من الأحرف ، من أجل الراحة ، نفترض أن السلسلة تنتهي دائمًا بحرف فارغ:


 template<size_t Size> using static_string = std::array<const char, Size>; constexpr static_string<6> hello = {'H', 'e', 'l', 'l', 'o', '\0'}; 

هنا يمكنك الذهاب بطريقة مختلفة وتحديد السلسلة على أنها مجموعة من الشخصيات. بدا لي هذا الخيار أكثر استهلاكا للوقت وأقل ملاءمة. لذلك ، لن يتم النظر فيه هنا.


إنشاء سلسلة ثابتة


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


 constexpr auto hello = make_static_string("hello"); 

هنا سوف يساعدنا أحد نماذج القالب المتغير ، مما يسمح لنا بتوسيع وسيطات القالب كمؤشرات للتهيئة الإجمالية لسلسلة ثابتة لدينا من سلسلة حرفية:


 template<size_t Size, size_t ... Indexes> constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const char (& str)[Size]) { return {str[Indexes] ..., '\0'}; } constexpr auto hello = make_static_string<0, 1, 2, 3, 4>("hello"); // hello == "hello" 

أفضل بالفعل ، ولكن لا يزال يتعين كتابة الفهارس يدويًا. هنا نلاحظ أيضًا أنه إذا لم تحدد جميع المؤشرات ، يمكنك الحصول على سلسلة فرعية من السلسلة الحرفية ، وإذا قمت بكتابتها بترتيب عكسي ، فعندها معكوس:


 constexpr hello1 = make_static_string<1, 2, 3>("hello"); // hello1 == "ell" constexpr hello2 = make_static_string<4, 3, 2, 1, 0>("hello"); // hello2 == "olleh" 

سيكون هذا الاعتبار مفيدًا جدًا لنا في المستقبل.


الآن نحن بحاجة إلى إنشاء سلسلة من مؤشرات الصف بطريقة أو بأخرى. للقيام بذلك ، تطبيق خدعة الميراث. حدد بنية فارغة (تحتاج إلى وراثة شيء ما) مع مجموعة من المؤشرات المطلوبة كمعلمات قالب:


 template<size_t ... Indexes> struct index_sequence {}; 

نحدد بنية المولد التي ستولد الفهارس واحدًا تلو الآخر ، وتخزين العداد في المعلمة الأولى:


 template<size_t Size, size_t ... Indexes> struct make_index_sequence : make_index_sequence<Size - 1, Size - 1, Indexes ...> {}; 

سنهتم أيضًا بنقطة نهاية العودية ، عندما يتم إنشاء جميع المؤشرات (العداد صفر) ، نتجاهل العداد ويتحول المولد إلى التسلسل الذي نحتاجه:


 template<size_t ... Indexes> struct make_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {}; 

ونتيجة لذلك ، ستبدو وظيفة إنشاء سلسلة ثابتة كما يلي:


 template<size_t Size, size_t ... Indexes> constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const char (& str)[Size], index_sequence<Indexes ...>) { return {str[Indexes] ..., '\0'}; } 

سنكتب دالة مشابهة لسلسلة ثابتة ، وسوف تكون مفيدة لنا أكثر:


 template<size_t Size, size_t ... Indexes> constexpr static_string<sizeof ... (Indexes) + 1> make_static_string(const static_string<Size>& str, index_sequence<Indexes ...>) { return {str[Indexes] ..., '\0'}; } 

علاوة على ذلك ، لكل دالة تأخذ السلسلة الحرفية foo(const char (& str)[Size]) foo(const static_string<Size>& str) دالة مماثلة تأخذ السلسلة الثابتة foo(const static_string<Size>& str) . ولكن للإيجاز ، لن أذكر ذلك.


نظرًا لأن طول السلسلة الحرفية معروف لنا ، يمكننا تلقائيًا إنشاء سلسلة من المؤشرات ، سنكتب غلافًا للدالة أعلاه:


 template<size_t Size> constexpr static_string<Size> make_static_string(const char (& str)[Size]) { return make_static_string(str, make_index_sequence<Size - 1>{}); } 

تسمح لنا هذه الوظيفة بالقيام بما نريده بالضبط في بداية الفصل.


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


 constexpr static_string<1> make_static_string() { return {'\0'}; } 

نحتاج أيضًا إلى إنشاء سلسلة من مجموعة من الأحرف:


 template<char ... Chars> constexpr static_string<sizeof ... (Chars) + 1> make_static_string(char_sequence<Chars ...>) { return {Chars ..., '\0'}; } 

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


إخراج سلسلة ثابتة إلى دفق


كل شيء بسيط هنا. نظرًا لأن السلسلة الخاصة بنا تنتهي بحرف فارغ ، يكفي إخراج بيانات الصفيف إلى الدفق:


 template<size_t Size> std::ostream& operator<<(std::ostream& os, const static_string<Size>& str) { os << str.data(); return os; } 

تحويل السلسلة الثابتة إلى std :: string


لا شيء معقد هنا أيضًا. نقوم بتهيئة السلسلة ببيانات الصفيف:


 template<size_t Size> std::string to_string(const static_string<Size>& str) { return std::string(str.data()); } 

مقارنة السلسلة الثابتة


سنقوم بمقارنة السطور حرفًا بحرف حتى نجد الاختلافات ، أو حتى نصل إلى نهاية سطر واحد على الأقل. نظرًا لأن كونستركسبر لـ لم يتم اختراعه بعد ، فإننا نستخدم العودية والعامل الثلاثي:


 template<size_t Size1, size_t Size2> constexpr int static_string_compare( const static_string<Size1>& str1, const static_string<Size2>& str2, int index = 0) { return index >= Size1 && index >= Size2 ? 0 : index >= Size1 ? -1 : index >= Size2 ? 1 : str1[index] > str2[index] ? 1 : str1[index] < str2[index] ? -1 : static_string_compare(str1, str2, index + 1); } 

في المستقبل ، سنحتاج إلى نسخة موسعة من المقارنة ، وسنقدم فهرسًا فرديًا لكل سطر من خطوطها ، وسنحد أيضًا من عدد الأحرف المقارنة:


 template<size_t Size1, size_t Size2> constexpr int static_string_compare( const static_string<Size1>& str1, size_t index1, const static_string<Size2>& str2, size_t index2, size_t cur_length, size_t max_length) { return cur_length > max_length || (index1 >= Size1 && index2 >= Size2) ? 0 : index1 >= Size1 ? -1 : index2 >= Size2 ? 1 : str1[index1] > str2[index2] ? 1 : str1[index1] < str2[index2] ? -1 : static_string_compare(str1, index1 + 1, str2, index2 + 1, cur_length + 1, max_length); } 

هذا الإصدار من المقارنة سيسمح لنا بمقارنة ليس فقط السلسلة بأكملها ، ولكن أيضًا سلاسل فرعية فردية.


تسلسل سلاسل ثابتة


للتسلسل ، نستخدم نفس القالب المتغير كما هو الحال في الفصل الخاص بإنشاء سلسلة ثابتة. قم بتهيئة الصفيف أولاً بأحرف السطر الأول (باستثناء الحرف الفارغ) ، ثم الثاني ، وأخيرًا قم بإضافة الحرف الفارغ إلى النهاية:


 template<size_t Size1, size_t ... Indexes1, size_t Size2, size_t ... Indexes2> constexpr static_string<Size1 + Size2 - 1> static_string_concat_2( const static_string<Size1>& str1, index_sequence<Indexes1 ...>, const static_string<Size2>& str2, index_sequence<Indexes2 ...>) { return {str1[Indexes1] ..., str2[Indexes2] ..., '\0'}; } template<size_t Size1, size_t Size2> constexpr static_string<Size1 + Size2 - 1> static_string_concat_2( const static_string<Size1>& str1, const static_string<Size2>& str2) { return static_string_concat_2(str1, make_index_sequence<Size1 - 1>{}, str2, make_index_sequence<Size2 - 1>{}); } 

ننفذ أيضًا نموذجًا متغيرًا لسَلسَلة عدد عشوائي من السلاسل أو حرفية السلسلة:


 constexpr auto static_string_concat() { return make_static_string(); } template<typename Arg, typename ... Args> constexpr auto static_string_concat(Arg&& arg, Args&& ... args) { return static_string_concat_2(make_static_string(std::forward<Arg>(arg)), static_string_concat(std::forward<Args>(args) ...)); } 

عمليات بحث السلسلة الثابتة


خذ بعين الاعتبار عملية البحث عن حرف وسلسلة فرعية في سلسلة ثابتة.


ابحث عن حرف في سلسلة ثابتة


البحث عن الأحرف ليس صعبًا بشكل خاص ، تحقق بشكل متكرر من الأحرف لجميع المؤشرات وأرجع الفهرس الأول في حالة المطابقة. سنعطي أيضًا الفرصة لتعيين الموضع الأولي للبحث ورقم تسلسل المباراة:


 template<size_t Size> constexpr size_t static_string_find(const static_string<Size>& str, char ch, size_t from, size_t nth) { return Size < 2 || from >= Size - 1 ? static_string_npos : str[from] != ch ? static_string_find(str, ch, from + 1, nth) : nth > 0 ? static_string_find(str, ch, from + 1, nth - 1) : from; } 

يشير ثابت static_string_npos إلى أن البحث لم ينجح. نحدده على النحو التالي:


 constexpr size_t static_string_npos = std::numeric_limits<size_t>::max(); 

وبالمثل ، نقوم بتنفيذ بحث في الاتجاه المعاكس:


 template<size_t Size> constexpr size_t static_string_rfind(const static_string<Size>& str, char ch, size_t from, size_t nth) { return Size < 2 || from > Size - 2 ? static_string_npos : str[from] != ch ? static_string_rfind(str, ch, from - 1, nth) : nth > 0 ? static_string_rfind(str, ch, from - 1, nth - 1) : from; } 

تحديد تواجد حرف في سلسلة ثابتة


لتحديد حدوث حرف ما ، حاول البحث عنه:


 template<size_t Size> constexpr bool static_string_contains(const static_string<Size>& str, char ch) { return static_string_find(str, ch) != static_string_npos; } 

حساب عدد مرات ظهور حرف في سلسلة ثابتة


يتم تنفيذ عدد مرات التكرار بشكل تافه:


 template<size_t Size> constexpr size_t static_string_count(const static_string<Size>& str, char ch, size_t index) { return index >= Size - 1 ? 0 : (str[index] == ch ? 1 : 0) + static_string_count(str, ch, index + 1); } 

ابحث عن سلسلة فرعية في سلسلة ثابتة


نظرًا لافتراض أن الصفوف الثابتة ستكون صغيرة نسبيًا ، فلن نطبق خوارزمية كنوت موريس برات هنا ، سنقوم بتطبيق أبسط خوارزمية من الدرجة الثانية:


 template<size_t Size, size_t SubSize> constexpr size_t static_string_find(const static_string<Size>& str, const static_string<SubSize>& substr, size_t from, size_t nth) { return Size < SubSize || from > Size - SubSize ? static_string_npos : static_string_compare(str, from, substr, 0, 1, SubSize - 1) != 0 ? static_string_find(str, substr, from + 1, nth) : nth > 0 ? static_string_find(str, substr, from + 1, nth - 1) : from; } 

وبالمثل ، نقوم بتنفيذ بحث في الاتجاه المعاكس:


 template<size_t Size, size_t SubSize> constexpr size_t static_string_rfind(const static_string<Size>& str, const static_string<SubSize>& substr, size_t from, size_t nth) { return Size < SubSize || from > Size - SubSize ? static_string_npos : static_string_compare(str, from, substr, 0, 1, SubSize - 1) != 0 ? static_string_rfind(str, substr, from - 1, nth) : nth > 0 ? static_string_rfind(str, substr, from - 1, nth - 1) : from; } 

تحديد حدوث سلسلة فرعية في سلسلة ثابتة


لتحديد حدوث سلسلة فرعية ، حاول البحث عنها:


 template<size_t Size, size_t SubSize> constexpr bool static_string_contains(const static_string<Size>& str, const static_string<SubSize>& substr) { return static_string_find(str, substr) != static_string_npos; } 

تحديد ما إذا كانت السلسلة الثابتة تبدأ / تنتهي بـ / على سلسلة فرعية معينة


بتطبيق المقارنة الموصوفة سابقًا ، يمكننا تحديد ما إذا كانت السلسلة الثابتة تبدأ بالسلسلة الفرعية المحددة:


 template<size_t SubSize, size_t Size> constexpr bool static_string_starts_with(const static_string<Size>& str, const static_string<SubSize>& prefix) { return SubSize > Size ? false : static_string_compare(str, 0, prefix, 0, 1, SubSize - 1) == 0; } 

وبالمثل لإنهاء خط ثابت:


 template<size_t SubSize, size_t Size> constexpr bool static_string_ends_with(const static_string<Size>& str, const static_string<SubSize>& suffix) { return SubSize > Size ? false : static_string_compare(str, Size - SubSize, suffix, 0, 1, SubSize - 1) == 0; } 

العمل مع سلاسل فرعية ثابتة


هنا نلقي نظرة على العمليات المرتبطة بسلسلة فرعية من سلسلة ثابتة.


الحصول على سلسلة فرعية وبادئة ولاحقة سلسلة ثابتة


كما أشرنا سابقًا ، للحصول على سلسلة فرعية ، تحتاج إلى إنشاء سلسلة من المؤشرات ، مع مؤشرات البداية والنهاية المحددة:


 template<size_t Begin, size_t End, size_t ... Indexes> struct make_index_subsequence : make_index_subsequence<Begin, End - 1, End - 1, Indexes ...> {}; template<size_t Pos, size_t ... Indexes> struct make_index_subsequence<Pos, Pos, Indexes ...> : index_sequence<Indexes ...> {}; 

ننفذ الحصول على سلسلة فرعية مع التحقق من بداية ونهاية سلسلة فرعية باستخدام static_assert :


 template<size_t Begin, size_t End, size_t Size> constexpr auto static_string_substring(const static_string<Size>& str) { static_assert(Begin <= End, "Begin is greater than End (Begin > End)"); static_assert(End <= Size - 1, "End is greater than string length (End > Size - 1)"); return make_static_string(str, make_index_subsequence<Begin, End>{}); } 

البادئة هي سلسلة فرعية تتزامن بدايتها مع بداية الخط الثابت الأصلي:


 template<size_t End, size_t Size> constexpr auto static_string_prefix(const static_string<Size>& str) { return static_string_substring<0, End>(str); } 

وبالمثل بالنسبة لللاحقة ، لا تتطابق النهاية إلا:


 template<size_t Begin, size_t Size> constexpr auto static_string_suffix(const static_string<Size>& str) { return static_string_substring<Begin, Size - 1>(str); } 

تقسيم سلسلة ثابتة إلى جزئين في فهرس معين


لتقسيم سلسلة ثابتة في فهرس معين ، يكفي إرجاع البادئة واللاحقة:


 template<size_t Index, size_t Size> constexpr auto static_string_split(const static_string<Size>& str) { return std::make_pair(static_string_prefix<Index>(str), static_string_suffix<Index + 1>(str)); } 

عكس السلسلة الثابتة


لعكس سلسلة ثابتة ، نكتب مولد الفهرس الذي يولد الفهارس بالترتيب العكسي:


 template<size_t Size, size_t ... Indexes> struct make_reverse_index_sequence : make_reverse_index_sequence<Size - 1, Indexes ..., Size - 1> {}; template<size_t ... Indexes> struct make_reverse_index_sequence<0, Indexes ...> : index_sequence<Indexes ...> {}; 

الآن نقوم بتطبيق دالة تعكس السلسلة الثابتة:


 template<size_t Size> constexpr auto static_string_reverse(const static_string<Size>& str) { return make_static_string(str, make_reverse_index_sequence<Size - 1>{}); } 

حساب تجزئة السلسلة الثابتة


سنقوم بحساب التجزئة باستخدام الصيغة التالية:


H (s) = (s 0 + 1) ⋅ 33 0 + (s 1 + 1) ⋅ 33 1 + ... + (s n - 1 + 1) ⋅ 33 n - 1 + 5381 ⋅ 33 n mod 2 64


 template<size_t Size> constexpr unsigned long long static_string_hash(const static_string<Size>& str, size_t index) { return index >= Size - 1 ? 5381ULL : static_string_hash(str, index + 1) * 33ULL + str[index] + 1; } 

تحويل رقم إلى سلسلة ثابتة والعكس صحيح


في هذا الفصل ، ننظر في تحويل سلسلة ثابتة إلى عدد صحيح ، وكذلك معكوس. من أجل البساطة ، نفترض أن الأرقام ممثلة بأنواع long long وغير unsigned long long ، فهذه أنواع ذات سعة كبيرة ، أي أنها مناسبة لمعظم الحالات.


تحويل رقم إلى سلسلة ثابتة


لتحويل رقم إلى سلسلة ثابتة ، نحتاج إلى الحصول على جميع أرقام الرقم ، وتحويلها إلى الأحرف المقابلة وإنشاء سلسلة من هذه الأحرف.


للحصول على جميع أرقام الرقم ، سنستخدم مولدًا مشابهًا لمولد تسلسل الفهرس. تحديد تسلسل الحروف:


 template<char ... Chars> struct char_sequence {}; 

نقوم بتطبيق مولد حرف الأرقام ، وتخزين الرقم الحالي في المعلمة الأولى ، والأرقام في ما يلي ، يتم إضافة الرقم التالي إلى بداية التسلسل ، ويتم تقسيم الرقم على عشرة:


 template<unsigned long long Value, char ... Chars> struct make_unsigned_int_char_sequence : make_unsigned_int_char_sequence<Value / 10, '0' + Value % 10, Chars ...> {}; 

إذا كان الرقم الحالي هو 0 ، فإننا نتجاهله ، ونعيد سلسلة من الأرقام ، فلا يوجد شيء آخر لتحويله:


 template<char ... Chars> struct make_unsigned_int_char_sequence<0, Chars ...> : char_sequence<Chars ...> {}; 

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


 template<> struct make_unsigned_int_char_sequence<0> : char_sequence<'0'> {}; 

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


 template<bool Negative, long long Value, char ... Chars> struct make_signed_int_char_sequence {}; 

سنقوم بمعالجة الرقم كما هو موضح أعلاه ، ولكن مع مراعاة العلامة:


 template<long long Value, char ... Chars> struct make_signed_int_char_sequence<true, Value, Chars ...> : make_signed_int_char_sequence<true, Value / 10, '0' + -(Value % 10), Chars ...> {}; template<long long Value, char ... Chars> struct make_signed_int_char_sequence<false, Value, Chars ...> : make_signed_int_char_sequence<false, Value / 10, '0' + Value % 10, Chars ...> {}; 

هناك نقطة خفية هنا ، انتبه إلى -(Value % 10) . هنا ، لا يمكنك -Value % 10 ، نظرًا لأن نطاق الأرقام السالبة هو رقم واحد أوسع من نطاق الإيجابيات ويقع الحد الأدنى لمعامل الرقم خارج مجموعة القيم الصالحة.


نتجاهل الرقم بعد المعالجة ، إذا كان سالبًا ، أضف رمز علامة الطرح:


 template<char ... Chars> struct make_signed_int_char_sequence<true, 0, Chars ...> : char_sequence<'-', Chars ...> {}; template<char ... Chars> struct make_signed_int_char_sequence<false, 0, Chars ...> : char_sequence<Chars ...> {}; 

بشكل منفصل ، نحن نهتم بتحويل الصفر:


 template<> struct make_signed_int_char_sequence<false, 0> : char_sequence<'0'> {}; 

أخيرًا ، نقوم بتنفيذ وظائف التحويل:


 template<unsigned long long Value> constexpr auto uint_to_static_string() { return make_static_string(make_unsigned_int_char_sequence<Value>{}); } template<long long Value> constexpr auto int_to_static_string() { return make_static_string(make_signed_int_char_sequence<(Value < 0), Value>{}); } 

تحويل السلسلة الثابتة إلى رقم


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


 template<size_t Size> constexpr unsigned long long static_string_to_uint(const static_string<Size>& str, size_t index) { return Size < 2 || index >= Size - 1 ? 0 : (str[index] - '0') + 10ULL * static_string_to_uint(str, index - 1); } template<size_t Size> constexpr unsigned long long static_string_to_uint(const static_string<Size>& str) { return static_string_to_uint(str, Size - 2); } 

لتحويل الأرقام الموقعة ، لاحظ أن الأرقام السالبة تبدأ بعلامة الطرح:


 template<size_t Size> constexpr long long static_string_to_int(const static_string<Size>& str, size_t index, size_t first) { return index < first || index >= Size - 1 ? 0 : first == 0 ? (str[index] - '0') + 10LL * static_string_to_int(str, index - 1, first) : -(str[index] - '0') + 10LL * static_string_to_int(str, index - 1, first); } template<size_t Size> constexpr long long static_string_to_int(const static_string<Size>& str) { return Size < 2 ? 0 : str[0] == '-' ? static_string_to_int(str, Size - 2, 1) : static_string_to_int(str, Size - 2, 0); } 

اعتبارات سهولة استخدام المكتبة


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


كائن سلسلة ثابت


احزم السلسلة والطرق المنفذة في كائن. سيسمح هذا باستخدام أسماء طرق أقصر ، بالإضافة إلى عوامل مقارنة المقارنة:


 template<size_t Size> struct static_string { constexpr size_t length() const { return Size - 1; } constexpr size_t size() const { return Size; } constexpr size_t begin() const { return 0; } constexpr size_t end() const { return Size - 1; } constexpr size_t rbegin() const { return Size - 2; } constexpr size_t rend() const { return std::numeric_limits<size_t>::max(); } constexpr bool empty() const { return Size < 2; } constexpr auto reverse() const { return static_string_reverse(*this); } template<size_t Begin, size_t End> constexpr auto substring() const { return static_string_substring<Begin, End>(*this); } template<size_t End> constexpr auto prefix() const { return static_string_prefix<End>(*this); } template<size_t Begin> constexpr auto suffix() const { return static_string_suffix<Begin>(*this); } constexpr size_t find(char ch, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t find(const static_string<SubSize>& substr, size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t find(const char (& substr)[SubSize], size_t from = 0, size_t nth = 0) const { return static_string_find(*this, substr, from, nth); } constexpr size_t rfind(char ch, size_t from = Size - 2, size_t nth = 0) const { return static_string_rfind(*this, ch, from, nth); } template<size_t SubSize> constexpr size_t rfind(const static_string<SubSize>& substr, size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } template<size_t SubSize> constexpr size_t rfind(const char (& substr)[SubSize], size_t from = Size - SubSize, size_t nth = 0) const { return static_string_rfind(*this, substr, from, nth); } constexpr bool contains(char ch) const { return static_string_contains(*this, ch); } template<size_t SubSize> constexpr bool contains(const static_string<SubSize>& substr) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool contains(const char (& substr)[SubSize]) const { return static_string_contains(*this, substr); } template<size_t SubSize> constexpr bool starts_with(const static_string<SubSize>& prefix) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool starts_with(const char (& prefix)[SubSize]) const { return static_string_starts_with(*this, prefix); } template<size_t SubSize> constexpr bool ends_with(const static_string<SubSize>& suffix) const { return static_string_ends_with(*this, suffix); } template<size_t SubSize> constexpr bool ends_with(const char (& suffix)[SubSize]) const { return static_string_ends_with(*this, suffix); } constexpr size_t count(char ch) const { return static_string_count(*this, ch); } template<size_t Index> constexpr auto split() const { return static_string_split<Index>(*this); } constexpr unsigned long long hash() const { return static_string_hash(*this); } constexpr char operator[](size_t index) const { return data[index]; } std::string str() const { return to_string(*this); } std::array<const char, Size> data; }; 


. :


 template<size_t Size1, size_t Size2> constexpr bool operator<(const static_string<Size1>& str1, const static_string<Size2>& str2) { return static_string_compare(str1, str2) < 0; } 

> <= >= == !=, . - .



:


 #define ITOSS(x) int_to_static_string<(x)>() #define UTOSS(x) uint_to_static_string<(x)>() #define SSTOI(x) static_string_to_int((x)) #define SSTOU(x) static_string_to_uint((x)) 


.


:


 constexpr auto hello = make_static_string("Hello"); constexpr auto world = make_static_string("World"); constexpr auto greeting = hello + ", " + world + "!"; // greeting == "Hello, World!" 

, :


 constexpr int apples = 5; constexpr int oranges = 7; constexpr auto message = static_string_concat("I have ", ITOSS(apples), " apples and ", ITOSS(oranges), ", so I have ", ITOSS(apples + oranges), " fruits"); // message = "I have 5 apples and 7 oranges, so I have 12 fruits" 

 constexpr unsigned long long width = 123456789ULL; constexpr unsigned long long height = 987654321ULL; constexpr auto message = static_string_concat("A rectangle with width ", UTOSS(width), " and height ", UTOSS(height), " has area ", UTOSS(width * height)); // message = "A rectangle with width 123456789 and height 987654321 has area 121932631112635269" 

 constexpr long long revenue = 1'000'000LL; constexpr long long costs = 1'200'000LL; constexpr long long profit = revenue - costs; constexpr auto message = static_string_concat("The first quarter has ended with net ", (profit >= 0 ? "profit" : "loss "), " of $", ITOSS(profit < 0 ? -profit : profit)); // message == "The first quarter has ended with net loss of $200000" 

URL:


 constexpr auto url = make_static_string("http://www.server.com:8080"); constexpr auto p = url.find("://"); constexpr auto protocol = url.prefix<p>(); // protocol == "http" constexpr auto sockaddr = url.suffix<p + 3>(); constexpr auto hp = sockaddr.split<sockaddr.find(':')>(); constexpr auto host = hp.first; // host == "www.server.com" constexpr int port = SSTOI(hp.second); // port == 8080 

:


 constexpr auto str = make_static_string("Hello"); for (size_t i = str.begin(); i != str.end(); ++i) //  std::cout << str[i]; std::cout << std::endl; // Hello for (size_t i = str.rbegin(); i != str.rend(); --i) //  std::cout << str[i]; std::cout << std::endl; // olleH 

المراجع


, , github


, .


Update


_ss :


 template<typename Char, Char ... Chars> constexpr basic_static_string<Char, sizeof ... (Chars) + 1> operator"" _ss() { return {Chars ..., static_cast<Char>('\0')}; }; 

make_static_string() , :


 constexpr auto hello_world = "Hello"_ss + " World"; if ("Hello" < "World"_ss) { ... } constexpr auto hash = "VeryLongString"_ss.hash(); 

Char char:


 template<typename Char, size_t Size> struct basic_static_string { // ... std::array<const Char, Size> data; }; 

char whar_t, , concat, :


 template<size_t Size> using static_string_t = basic_static_string<char, Size>; template<size_t Size> using static_wstring_t = basic_static_string<wchar_t, Size>; using static_string = basic_static_string<char, 0>; using static_wstring = basic_static_string<wchar_t, 0>; 

"" :


 constexpr auto wide_string = L"WideString"_ss; constexpr int apples = 5; constexpr int oranges = 7; constexpr int fruits = apples + oranges; constexpr auto str3 = static_wstring::concat(L"I have ", ITOSW(apples), L" apples and ", ITOSW(oranges), L" oranges, so I have ", ITOSW(fruits), L" fruits"); static_assert(str3 == L"I have 5 apples and 7 oranges, so I have 12 fruits", ""); std::wcout << str3 << std::endl; 

size(), size() length() , sizeof():


 constexpr auto ss1 = "Hello"_ss; static_assert(ss1.length() == 5, ""); static_assert(ss1.size() == 5, ""); static_assert(sizeof(ss1) == 6, ""); 

github
.


Update 2


AndreySu , :


 #include <iostream> using namespace std; template<typename Char, Char ... Chars> struct static_string{}; template<typename Char, Char ... Chars1, Char ... Chars2> constexpr static_string<Char, Chars1 ..., Chars2 ... > operator+( const static_string<Char, Chars1 ... >& str1, const static_string<Char, Chars2 ... >& str2) { return static_string<Char, Chars1 ..., Chars2 ...>{}; } template<typename Char, Char ch, Char ... Chars> std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& bos, const static_string<Char, ch, Chars ...>& str) { bos << ch << static_string<Char, Chars ... >{}; return bos; } template<typename Char> std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& bos, const static_string<Char>& str) { return bos; } template<typename Char, Char ... Chars> constexpr static_string<Char, Chars ... > operator"" _ss() { return static_string<Char, Chars ... >{}; }; int main() { constexpr auto str1 = "abc"_ss; constexpr auto str2 = "def"_ss; constexpr auto str = str1 + str2 + str1; std::cout << str << std::endl; return 0; } 

, , - .

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


All Articles