قابلیتهای سی پلاس پلاس (++C) که باید بدانید – با مثال و به زبان ساده
اگر آشنایی ابتدایی با زبان برنامهنویسی ++C دارید و میخواهید با قابلیت های سی پلاس پلاس نسخه 20 یعنی جدیدترین نسخه این زبان بهتر آشنا شوید و خلاصه کوتاهی در مورد هر یک از این قابلیتها در یک راهنمای جامع مطالعه کنید، مطلب خوبی را برای خواندن انتخاب کردهاید. این مطلب در واقع یک برگه تقلب در مورد امکانات و قابلیتهای C++20 است. اگر به تازگی با این زبان برنامهنویسی آشنا شدید و یا میخواهید این زبان را به روشی اصولیتر بیاموزید، پیشنهاد میکنیم کار خود را از آموزش زیر آغاز کنید:


مفاهیم کلی
در این بخش مواردی که کامپایلر از یک آرگومان تمپلیت نیاز دارد تا پیش از «وهلهسازی» (instantiation) بررسی کند را توضیح میدهیم. در نتیجه این کار در صورت نیاز به نمایش پیام خطا، آن پیام روشنتر خواهد بود.
برای نمونه میتوانیم به کاربر اعلام کنیم که شرط X احراز نشده است. تا پیش از C++20 میتوانستیم از یک میانبر استفاده کرده و از سازههای enable_if بهره بگیریم و در غیر این صورت باید در زمان وهلهسازی با نمایش یک پیام خطای رمزآلود برنامه را متوقف میساختیم. با معرفی کانسپتها، این نقطه شکست بسیار زودتر رخ میدهد و لذا پیام خطا روشنتر خواهد بود.

عبارت Requires
کار خود را با requires-expression آغاز میکنیم.
این عبارت شامل الزامات واقعی آرگومانهای تمپلیت است و در صورتی که این الزامات برآورده شوند، مقدار true و در غیر این صورت مقدار false بازگشت میدهد.
کانسپت
Concept در واقع یک مجموعه نامدار از چنین قیود یا ترکیب منطقی آنها است. هم کانسپت و هم requires-expression به صورت یک مقدار بولی در زمان کامپایل رندر میشوند و میتوانند مانند یک مقدار نرمال، برای نمونه به صورت if constexpr مورد استفاده قرار گیرند:
بند Requires
برای این که در عمل چیزی را مقید بکنیم باید از «بند الزام» (requires-clause) استفاده کنیم. این بند ممکن است درست پس از بلوک <>template ظاهر شود یا به عنوان آخرین عنصر اعلان تابع یا حتی به طور همزمان در هر دو جا دارای لامبدا باشد.
روش تمیزتر این است که از نام concept به جای کلیدواژه class/typename در لیست پارامتر تمپلیت استفاده کنیم:
پارامترهای تمپلیت نیز میتوانند مقید شوند. در این حالت آرگومان باید حداکثر به اندازه پارامتر مقید شود. پارامترهای نامقید تمپلیت همچنان میتوانند تمپلیتهای مقید را به عنوان آرگومان بپذیرند:
تابعهای دارای قیود برآورد نشده، ناپیدا میشوند:
پارامترهای auto مقید
در نسخه جدید زبان برنامهنویسی ++C پارامترهای auto برای تابعهای نرمال مجاز شمرده شدهاند تا آنها را مانند لامبداهای ژنریک به صورت ژنریک دربیاورند. کانسپتها نیز میتوانند برای مقید ساختن انواع placeholder در چارچوبهای مختلف استفاده شوند. در پکهای پارامتری MyConcept... Ts الزام میکند که MyConcept برای هر عنصر پک به صورت تک به تک true ارزیابی شود و نه برای کل پک به صورت یکجا. برای نمونه باید به صورت requires<T1> && requires<T2> && ... && requires<TLast> باشد.
مرتبسازی جزئی به وسیله قیود
قیود علاوه بر تعیین الزامات برای یک اعلان منفرد میتوانند برای انتخاب بهترین جایگزین برای یک تابع نرمال، تابع تمپلیت یا یک تمپلیت کلاس نیز استفاده شوند. به این منظور قیدها یک نماد مرتبسازی جزئی دارند. به این ترتیب یک قید میتواند «کمتر» یا «بیشتر» از قید دیگری محدود شود و یا این که نامرتب باشد. کامپایلر قیدها را به صورت یک عطف/فصل اتمیک از قیدها تجزیه میکند. به طور شهودی C1 && C2 مقیدتر از C1 است، C1 مقیدتر از C1 || C2 است و هر قید مقیدتر از اعلان نامقید است. هنگامی که یک نامزد با قید برآورده شده وجود داشته باشد، آن که مقیدتر است انتخاب میشود. اگر قیدها نامرتب باشند، کاربرد مبهم خواهد بود.
درک شیوه تجزیه قیدها از سوی کامپایلر حائز اهمیت است. زمانی که کامپایلر ببیند قیدهای اتمیک مشترک دارد، بین آنها استنتاج میکند. در طی زمان تجزیه، نام کانسپت با تعریف آن جایگزین میشود، اما requires-expression دیگر بیش از این تجزیه نخواهد شد. دو قید اتمیک تنها زمانی یکسان هستند که با عبارت واحدی در مکان یکسان بازنمایی شده باشند. برای نمونه concept C = C1 && C2 به عطف C1 و C2 تجزیه میشود، اما concept C = requires{...} به concept C = Expression-Location-Pair تبدیل میشود و بدنه آن دیگر تجزیه نمیشود. اگر دو کانسپت الزامات مشترک یا حتی یکسانی در requires-expression خود داشته باشند، همواره نامرتب خواهند بود، زیرا یا مقادیر requires-expression آنها برابر نیست و یا اگر هم برابر باشد، در مکانهای مبدأ متفاوتی قرار دارد. همین اتفاق در زمان کاربرد تکراری خصیصههای از نوع عریان رخ میدهد، زیرا این موارد همواره قیدهای اتمیک متفاوتی را به جهت مکانهای خود نمایش میدهند و از این رو نمیتوانند به منظور مرتبسازی مورد استفاده قرار گیرند.
تابعهhی عضو خاص بدیهی شرطی
در مورد پوششهایی (wrapper) مانند std::optional یا std::variant بهتر است میزان «بدیهی بودن» از روی انواعی که پوشش میدهند مشخص شود. برای نمونه <std::optional<int باید «بدیهی» (trivial) باشد، اما <std::optional<std::string نباید چنین باشد. در C++17 این کار از طریق استفاده از pretty cumbersome machinery انجام مییافت. کانسپتها یک راهحل طبیعی برای این منظور دارند. ما امکان ساخت چند نسخه از یک تابع عضویت واحد را با قیدهای مختلف داریم و کامپایلر بهترین تابع را انتخاب کرده و بقیه را نادیده میگیرد. در این مورد خاص، وقتی نوع پوشش یافته از نوع بدیهی باشد، به یک مجموعه بدیهی از تابعها نیاز داریم و هنگامی که چنین نباشد به یک مجموعه نابدیهی نیاز داریم. در C++17 یک کلاس کپیپذیر از نظر بدیهی بودن یا باید همه عملیاتهای کپی و جابجاییاش حذف شود و یا بدیهی باشد. برای این که کانسپتها را نیز در نظر بگیریم، مفهوم «تابع عضویت خاص مطلوب» (eligible special member function) معرفی شده است. این تابعی است که حذف نشده و قیدهای آن (در صورت وجود) برآورده شدهاند و هیچ تابع عضویت دیگری از این نوع با همان نوع پارامترها (در صورت وجود) مقیدتر نیست. به بیان سادهتر، این تابعی است که دارای مقیدترین قیدهای برآورده شده است. همه تجزیهگرهای موجود در این حالت «تجزیهگرهای پیشبین» (prospective destructors) نامیده میشوند. تنها یک تجزیهگر فعال مجاز است وجود داشته باشد و از طریق تصمیمگیری معمولی overload انتخاب میشود.
اکنون یک کلاس کپیپذیر از نظر بدیهی بودن به کلاسی گفته میشود که یک تجزیهگر حذف نشده و دست کم یک عملیات کپی/جابجایی مشروع دارد و همه چنین عملیاتهای مشروع آن از نوع بدیهی هستند. یک کلاس بدیهی زمانی از نظر بدیهی بودن کپیپذیر نامیده میشود که یک یا چند سازه پیشفرض مشروع داشته باشد که همگی بدیهی باشند. اساس طرز کار این تکنیک چنین است:
ماژولها
ماژولها یک روش جدید برای سازماندهی کد ++C در کامپوننتهای منطقی هستند. از نظر تاریخی ++C از مدل C استفاده میکند که بر مبنای پیشپردازشگر و شمول متنی مکرر است. این روش مشکلات زیادی از قبیل نشت ماکروها به سمت داخل و بیرون هدرها، هدرهای با وابستگی به ترتیب شمول، کامپایل شدن مکرر کد یکسان، وابستگیهای دوری، کپسولهسازی ضعیف جزییات پیادهسازی و بسیاری موارد دیگر دارد. ماژولها برای حل این مشکلات معرفی شدهاند ولی شاید این کار را با سرعت زیادی انجام ندهند. انتظار نمیرود تا زمانی که کامپایلرها و ابزارهای بیلد مانند CMake از ماژولها پشتیبانی نکردهاند، بتوانیم از همه توان آنها بهره بگیریم. توضیح کامل ماژولها فراتر از حیطه این مقاله است، ما در اینجا صرفاً برخی ایدههای مقدماتی را نشان میدهیم.
ایده اصلی خلق ماژولها، محدودسازی موارد دسترسپذیر (اکسپورت شده) در زمان استفاده (ایمپورت کردن) کلاینتها از یک ماژول بوده است. به این ترتیب میتوانیم جزییات پیادهسازی را به طور واقعی پنهان کنیم.
ماژولها چندان با ماکروها سازگار نیستند. شما نمیتوانید ماکروهایی که به طور دستی تعریف شدهاند را به ماژولها ارسال کنید و تنها در یک مورد خاص میتوانید ماکروها را از ماژول ایمپورت کنید. ماژولها نمیتوانند وابستگیهای دوری داشته باشند. ماژول یک موجودیت خود-گویا است. کامپایلر میتواند هر ماژول را دقیقاً یک بار پیش-کامپایل کند و از این رو زمان کلی کامپایل به میزان زیادی بهبود مییابد. ترتیب ایمپورت برای ماژولها اهمیتی ندارد.
واحدهای ماژول
ماژول میتواند یک واحد ماژول اینترفیس یا واحد ماژول پیادهسازی باشد. تنها واحدهای اینترفیس میتوانند در اینترفیس ماژول مشارکت داشته باشند. به همین دلیل است که در اعلان خود export میشوند. ماژول میتواند یک فایل منفرد یا روی پارتیشنهای مختلف پراکنده باشد. هر پارتیشن به شکل module_name:partition_name نامگذاری میشود. این پارتیشنها تنها درون همان ماژول قابل ایمپورت هستند و کلاینت تنها میتواند یک ماژول را ب طور کامل ایمپورت کند. این روش نسبت به فایلهای هدر کسپولهسازی بهتری ارائه میکند.
توجه کنید که پارتیشنها بدون ذکر نام ماژول ایمپورت شدهاند. به این ترتیب ایمپورت کردن پارتیشنهای ماژولهای دیگر ممنوع میشود. چندین واجد پیادهسازی مجاز هستند و همه واحدهای دیگر و پارتیشنها از هر نوع باید یکتا باشند. همه پارتیشنهای اینترفیس باید به وسیله ماژول و از طریق export import دوباره اکسپورت شوند.
اکسپورت
در این بخش شکلهای مختلف export را بررسی میکنیم. قاعده کلی این است که نمیتوانید نامهای دارای پیوند درونی را اکسپورت کنید:
ایمپورت
اعلانهای ایمپورت باید مقدم بر هر نوع اعلانهای غیر ماژولی باشند چون باعث تحلیل سریعتر وابستگی میشود. علاوه بر این که این روش کاملاً شهودی و سرراست است.
واحدهای هدر
یک import خاص وجود دارد که امکان ایمپورت کردن هدرهای قابل ایمپورت یعنی <import <header.h یا "import "header.h را فراهم میسازد. کامپایلر یک واحد هدر سنتز شده ایجاد کرده و همه اعلانها را به طور ضمنی اکسپورت میکند. این که در عمل کدام هدرها قابل ایمپورت هستند در مرحله پیادهسازی تعریف میشود، اما همه هدرهای کتابخانه ++C چنین هستند. شاید روشی باشد که بتوان به کامپایلر اعلام کرد کدام هدرهای ارائه شده از سوی کاربر قابل ایمپورت هستند. چنین هدرهایی نباید شامل تابعهای غیر «درون خطی» (inline) یا متغیرهایی با پیوند به بیرون باشند. این تنها شکلی از import است که امکان ایمپورت کردن ماکروها را از هدرها فراهم میسازد. با این حال همچنان نمیتوانید آنها را از طریق "export import "header.h مجدداً اکسپورت کنید. اگر در مورد محتوای هدر مطمئن نیستید، از این روش برای ایمپورت کردن هدرهای قدیمی به طور شانسی استفاده نکنید.
قطعه ماژول سراسری
اگر نیاز به استفاده از هدرهای سبک قدیم درون یک ماژول باشد، یک مکان مطمئن برای قرار دادن امن include-ها وجود دارد و آن «قطعه ماژول سراسری» (Global module fragment) است.
این دستور باید قبل از اعلان ماژول نامدار ظاهر شود و صرفاً میتواند شامل دایرکتیوهای پیشپردازشگر باشد. همه اعلانهایی که در قطعه ماژول سراسری قرار دارند و همچنین واحدهای بازگردانی غیر ماژولار به یک ماژول سراسری منفرد متصل میشوند. از این رو همه قواعد مربوط به هدرهای نرمال در این مورد نیز صادق است.
قطعه ماژول خصوصی
آخرین بخش عجیب ماجرا «قطعه ماژول خصوصی» (Private module fragment) است. هدف از این قطعه آن است که جزئیات پیادهسازی در یک ماژول تکفایلی پنهان شود. از لحاظ نظری، کلاینتها ممکن است نتوانند در صورت تغییر یافتن موارد مختلف درون فرگمان ماژول خصوصی آن را مجدداً کامپایل کنند.
حذف inline ضمنی
یکی دیگر از تغییرات جالب در C++20 با inline مرتبط است. تابعهای عضویت که درون بخش تعاریف کلاس تعریف میشوند، در صورت الصاق کلاس به یک ماژول نامدار دیگر نمیتوانند به طور ضمنی inline شوند. تابعهای inline در یک ماژول نامدار تنها میتوانند از نامهایی استفاده کنند که در معرض دید کلاینت باشند.
کوروتینها
در نهایت ما شاهد معرفی کوروتینهای غیر پشتهای (چون در هیپ ذخیره میشوند) در C++ 20 هستیم. در این نسخه از زبان برنامهنویسی سی پلاس پلاس تقریباً پایینترین سطح از API عرضه شده است و باقی کار بر عهده کاربر قرار گرفته است. کلیدواژههای co_await, co_yield و co_return و قواعد برای تعامل بین فراخواننده و فراخوانده شده هستند. این قواعد چنان سطح پایین هستند که توضیحان ها در این جا ضرورتی ندارد. خوشبختانه نسخه C++23 این شکاف را تا حدودی پر کرده است. تا آن زمان میتوان از کتابخانههای شخص ثالث استفاده کرد. برای نمونه در مثال زیر از کتابخانه cppcoro استفاده کردهایم:
مقایسه سهطرفه
تا پیش از C++20 برای عرضه عملیات مقایسه در یک کلاس، نیاز به پیادهسازی شش عملگر به صورت ==,!=, <, <=, >, >= بود. به طور معمول، چهار مورد از این عملگرها شامل کد قالبی هستند که بر حسب == و < عمل میکنند که شامل منطق مقایسه عملی هستند. رویه معمول این است که آنها را به صورت تابعهای آزادی پیادهسازی کنیم که const T& را میگیرند تا بتوانند انواع تبدیلپذیر را مقایسه کنند. اگر بخواهید از انواع غیر تبدیلپذیر نیز پشتیبانی کنید، باید دو مجموعه از تابع یعنی op(const T1&, const T2&) و op(const T2&, const T1&) را اضافه کنید که موجب میشود 18 عملگر مقایسه داشته باشیم. C++20 یک روش بهتر برای مدیریت و درک مقایسهها به ما میدهد. اکنون میتوانید روی operator<=>() و گاهی اوقات روی ()==operator متمرکز شوید. اینک <=>operator جدید که به نام عملگر فضاپیما مشهور است، مقایسه سه طرفه را اجرا میکند. این عملگر با یک فراخوانی منفرد اعلام میکند که آیا a کوچکتر، مساوی یا بزرگتر از b است و روش کار آن شبیه ()strcmp است. این عملگر دستهبندی مقایسه (در ادامه توضیح داده شده) بازگشت میدهد که میتواند با صفر مقایسه شود. کامپایلر با داشتن این امکان میتواند فراخوانیهای به <, <=, >, >= را با فراخوانیها به ()<=>operator جایگزین و نتیجه آن را بررسی کند. به این ترتیب a < b به صورت a <=> b < 0 درمیآید. همچنین فراخوانیها به ==,!= با عملگر ==() عوض میشود، یعنی a!= b به صورت (a == b)! درمیآید. با توجه به قواعد جدید وارسی، امکان مدیریت مقایسههای نامتقارن نیز وجود دارد. به این ترتیب با ارائه یک مقایسه T1::operator==(const T2&) به صورت منفرد، هر دو مقایسه T1 == T2 و T2 == T1 به دست میآید و همین نکته در مورد ()<=>operator نیز صدق میکند. اکنون باید حداکثر دو تابع بنویسید تا شش مقایسه بین انواع تبدیلپذیر انجام یابد. همچنین با نوشتن 2 تابع، 12 مقایسه بین انواع تبدیلناپذیر صورت میگیرد.
دستهبندی مقایسهها
به طور استاندارد سه دسته مقایسه وجود دارد، اما شما برای ایجاد دستهبندیهای سفارشی منعی ندارید. strong_ordering به این معنی است که دقیقاً یکی از مقایسههای a < b, a > b و a == b باید true باشد و اگر a == b، در این صورت f(a) == f(b) است. weak_ordering به این معنی است که دقیقاً یکی از a < b, a > b, a == b باید true باشد و اگر a == b در این صورت f(a) میتواند برابر با f(b) نباشد. چنین عناصر معادل هم هستند، اما برابر نیستند. partial_ordering به این معنی است که ممکن است هیچ کدام از a < b, a > b, a == b مقدار TRUE نداشته باشند و اگر == b در این صورت f(a) نمیتواند برابر با f(b) باشد. معنی این حرف آن است که برخی عناصر ممک است مقایسهناپذیر باشند. نکته مهم در اینجا آن است که f() نمایانگر تابعی است که تنها به خصوصیتهای برجسته دسترسی دارد. برای نمونه std::vector<int> به طور کامل مرتب شده به جز این که دو بردار با مقدار یکسان میتوانند ظرفیت متفاوتی داشته باشند. در اینجا ظرفیت یک خصوصیت برجسته نیست. نمونه یک نوع با مرتبسازی ضعیف CaseInsensitiveString است که میتواند رشته را همان طور که هست ذخیره کند و در عین حال به روش حساس به بزرگی/کوچکی حروف مقایسه کند. مثالی از نوع با مرتبسازی جزئی float/double است زیرا NaN با دیگر نوعها قابل مقایسه نیست. این دستهبندیهای نوعی سلسله مرhتf تشکیل میدهند، یعنی مرتبسازی قوی میتواند به مرتبسازی جزئی و مرتبسازی جزئی نیز میتواند به مرتبسازی ضعیف تبدیل شود.
مقایسههای پیشفرضی
برای مقایسهها نیز میتوان مانند تابعهای عضویت خاص، مقادیر پیشفرض تعریف کرد. در چنین مواردی مقایسهها به روش عضویت-گونه عمل کرده و همه اعضای دادهای غیر استاتیک زیرین را با عملگرهای متناظرشان مقایسه میکنند. ()<=>operator پیشفرضی یک عملگر ()==operator پیشفرضی نیز اعلان میکند و از این رو میتوان دستوری مانند ;auto operator<=>(const T&) const = default نوشت و همه شش عملگر مقایسهای را با معناشناسی عضویتگونه به دست آورد.
در کد فوق ()==operator که به طور ضمنی اعلان شده همان امضای ()<=>operator را دارد، به جز این که نوع بازگشتی bool است.
دستهبندی مقایسه استنتاج شده، ضعیفترین دستهبندی از اعضای نوع است.
آنها باید عضو یا دوست باشند و تنها دوستان میتوانند به صورت با مقدار دریافت شوند.
این موارد میتوانند درست مانند تابعهای عضویت خاص دارای مقادیر پیشفرض خارج از کلاس باشند.
عملگر پیشفرضی ()<=>operator که از ()<=>operator از اعضای کلاس یا ترتیببندی آنها استفاده میکند، میتواند با استفاده از ()==Member::operator و ()<Member::operator سنتز شود. توجه کنید که این مقدار تنها برای اعضا کار میکند و نه خود کلاس. از این رو ()<T::operator که موجود است هرگز در ()<=>T::operator استفاده نمیشود.
Union و اعضای رفرنس از این خصوصیت پشتیبانی نمیکنند.
عبارتهای لامبدا
در این بخش با نقش عبارتهای لامبدا در C++20 آشنا میشویم.
عبارت this زمانی که به صورت ضمنی دریافت شود همواره به صورت «با ارجاع» دریافت میشود هر چند از [=] استفاده شده باشد. برای رفع این سردرگمی، C++20 این رفتار را منسوخ کرده و امکان استفاده از [=, this] را فراهم ساخته که صراحت بیشتری دارد.
struct S
لیست پارامتر تمپلیت برای لامبداهای ژنریک
گاهی اوقات لامبداهای ژنریک بیش از حد ژنریک میشوند. C++20 امکان استفاده از ساختار آشناتر تمپلیت را برای معرفی مستقیم نامهای نوع فراهم ساخته است.
لامبداها در ساختارهای غیر ارزیابی شده
عبارتهای لامبدا میتوانند در ساختارهای غیر ارزیابی شده مانند ()sizeof(), typeid(), decltype و غیره نیز استفاده شوند. در این بخش برخی نکات کلیدی این قابلیت را برای یک مثال عملیاتی مشاهده میکنیم. مفهوم اصلی این است که لامبداها یک نوع ناشناس منحصربهفرد دارند، به طوری که دو لامبدا و نوعهای آنها هرگز با هم برابر نمیشوند.
در مثال زیر ()f یک شمارنده منفرد را در دو واحد ترجمه افزایش میدهد، زیرا تابع inline طوری عمل میکند که گویی تنها یک تعریف از آن وجود دارد. با این حال g_s از ODR تخطی میکند، زیرا گرچه یک تعریف از آن وجود دارد، اما همچنان چندین اعلان وجود دارند که متفاوت هستند، زیرا دو لامبدای مختلف در a.cpp و b.cpp وجود دارند و از این جهت S آرگومان تمپلیت غیر نوعی متفاوتی دارد:
لامبداهای پیشفرض بیحالت ساختپذیر و انتسابپذیر
لامبداهای بیحالت در C++20 در واقع لامبداهای ساختپذیر و انتسابپذیر پیشفرض هستند که امکان استفاده از یک نوع لامبدا را برای ساخت/انتساب در آینده فراهم میسازند. ما با استفاده از لامبداها در ساختارهای غیر ارزیابیشده میتوان نوعی لامبدا به دست آوریم که دارای decltype() باشد و در ادامه متغیری با این نوع ایجاد کنیم:
در این کد std::map یک نوع مقایسهگر را دریافت میکند تا در ادامه وهلهسازی کند. با این که در C++17 نیز میتوانستیم نوع لامبدا داشته باشیم، اما امکان ساختن وهله از این نوع وجود داشت زیرا لامبداها به طور پیشفرض ساختپذیر نبودند.
بسط پک در لامبدای init-capture
C++20 امکان دریافت پکهای پارامتری را در لامبداها تسهیل کرده است. تا C++20 این مقادیر را میتوانستیم به صورت «با مقدار» و یا «با ارجاع» و یا با به کار بستن برخی ترفندها را صورت نیاز به جابجایی بسته با std::tuple دریافت کرد. اکنون کار بسیار آسانتر شده است و میتوانیم پک init-capture را ایجاد کرده و آن را با پکی که میخواهیم دریافت کنیم وهلهسازی کنیم. این وهلهسازی محدود به std::move یا std::forward نیست و هر تابعی میتواند روی عناصر پک اعمال شود.
عبارتهای ثابت
تابعهای بیدرنگ (consteval)
با این که constexpr به طور ضمنی اشاره میکند که تابع میتواند در زمان کامپایل ارزیابی شود، اما consteval مشخص میکند که تابع باید صرفاً در زمان کامپایل ارزیابی شود. تابعهای virtual مجاز هستند که به صورت consteval باشند، اما میتوانند override شوند و یا صرفاً از سوی تابع consteval دیگری باطل شوند، یعنی ساخت ترکیبی از consteval و غیر consteval مجاز نیست. تابعهای تخریبگر و تخصیص/آزادسازی نمیتوانند consteval باشند.
تابع مجازی constexpr
تابعهای مجازی اکنون میتوانند constexpr باشند. تابع constexpr میتواند به صورت غیر constexpr و یا به طور برعکس باطل شود.
بلوکهای try-catch در constexpr
قرارگیری بلوکهای try-catch در نسخه جدید زبان برنامهنویسی سی پلاس پلاس درون تابعهای constexpr مجاز است، اما throw چنین نیست و از این رو بلوک catch در عمل نادیده گرفته میشود. این وضعیت برای مثال میتواند در ترکیب با constexpr new مفید باشد چون میتوانیم تابع منفردی داشته باشیم که در زمان اجرا/کامپایل کار میکند:
constexpr dynamic_cast و typeid چندریختی
از آنجا که تابعهای مجازی اکنون به صورت constexpr هستند، دلیلی وجود ندارد که dynamic_cast و typeid چندریختی درون constexpr مجاز باشد متأسفانه std::type_info هنوز هیچ عضو constexpr ندارد و از این رو فعلاً کاربرد کمی دارد.
تغییر دادن عضو فعال یک union درون constexpr
یکی دیگر از بهبودهایی که در خصوص عبارتهای ثابت رخ داده است، فراهم آمدن امکان تغییر دادن عضو فعال یک Union است، اما هنوز امکان خواندن یک عضو غیر فعال وجود ندارد، زیرا یک UB است و UB درون سازه constexpr مجاز نیست.

تخصیصهای constexpr
C++20 یک زیرساخت برای کانتینرهای constexpr فراهم ساخته است. در وهله نخست امکان استفاده از constexpr و حتی تخریبگرهای virtual constexpr برای «انواع لفظی» (literal types) وجود دارد. در وهله دوم امکان فراخوانی به ()std::allocator<T>::allocate و new-expression وجود دارد که در صورت آزاد شدن فضای تخصیص یافته در زمان کامپایل، موجب یک فراخوانی به یکی از موارد سراسری operator new میشود. معنی این حرف آن است که حافظه میتواند در زمان کامپایل تخصیص یابد، اما باید در زمان کامپایل نیز آزاد شود. در صورتی که نیاز باشد، دادههای نهایی در زمان اجرا مورد استفاده قرار گیرند، این وضعیت موجب بروز نوعی سردرگمی میشود. از این رو گریزی نیست جز ذخیرهسازی در نوعی کانتینر غیر تخصیصی مانند std::array و دریافت دوگانه در زمان کامپایل: بار اول برای دریافت اندازه دادهها و بار دون برای دریافت یک کپی واقعی از آن.
مقداردهی پیشفرض آزمایشی در تابعهای constexpr
در C++17 سازه constexpr علاوه بر دیگر الزامات باید همه اعضای دادهای غیر استاتیک خود را مقداردهی میکرد. این قاعده در C++20 حذف شده است، اما از آنجا که UB در سازه constexpr مجاز نیست، امکان خواندن از چنین اعضای مقداردهی نشده وجود ندارد و تنها میتوان آنها را نوشت.
اعلان asm ارزیابی نشده در تابعهای constexpr
اعلان asm اکنون میتواند در صورتی که در زمان کامپایل ارزیابی نشده باشد، درون تابع constexpr ظاهر شود. به این ترتیب میتوان هم کد زمان اجرا و هم کد زمان کامپایل را درون یک تابع منفرد همزمان داشت:
تابع std::is_constant_evaluated()
اکنون با استفاده از تابع ()std::is_constant_evaluated میتوانیم بررسی کنیم که آیا فراخوانی جاری درون یک سازه ارزیابی شده ثابت رخ داده است یا نه. البته ما وسوسه میشویم که از عبارت در زمان کامپایل استفاده کنیم، اما بر اساس مستندات سی پلاس پلاس هیچ تفاوت روشنی بین زمان اجرا و زمان کامپایل وجود ندارد. در عوض C++20 لیستی از عبارتها ارائه کرده است که آنها را «ارزیابی شده ثابت» در نظر میگیریم ین تابع در صورتی که در حال ارزیابی این موارد باشیم، مقدار true و در غیر این صورت مقدار False بازگشت میدهد.
باید مراقب بود که این تابع را مستقیماً درون چین عبارتهای با ارزیابی ثابتی استفاده نکرد. بر اساس تعریف این تابع، اگر درون چنین عبارتهایی فراخوانی شود، حتی در صورتی که تابع محصور دارای ارزیابی ثابتی نباشد، مقدار true بازگشت خواهد داد.
تجمیعها
در این بخش با خصوصیات «تجمیعها» (Aggregates) در C++20 آشنا میشویم.
ممنوعیت تجمیع در سازندههای اعلان شده از سوی کاربر
در نسخه جدید زبان ++C انواع تجمیع نمیتوانند سازندههای اعلان شده از سوی کاربر داشته باشند. تا پیش از این تجمیعها تنها مجاز به داشتن سازندههای حذف شده یا پیشفرض بودند. این امر موجب بروز رفتارهای عجیب در تجمیعهای دارای سازندههای حذفی یا پیشفرضی میشد، چون این تجمیعها از سوی کاربر اعلان میشدند اما در اختیار وی قرار نمیگرفتند.
استنتاج آرگومان تمپلیت کلاس برای تجمیعها
در C++17 برای استفاده از تجمیع با CTAD نیاز به راهنمای استنتاج صریح داشتیم که اکنون غیرضروری شده است:
اکنون در صورت وجود راهنماییهای استنتاج ارائه شده از سوی کاربر دیگر نیازی به CTAD نداریم.
اینک میتوان انواع آرایه را استنتاج کرد:
حذف آکولاد در مورد انواع غیر آرایهای وابسته یا انواع آرایهای با کران وابسته پاسخگو نیست.
اما حذف آکولاد در مورد بسط پکها کار میکند. عنصر با تجمیع متوالی که یک بسط پک هست با همه عناصر باقیمانده متناظر خواهد بود.
عنصر غیر متوالی که یک بسط پک باشد با هیچ عنصری متناظر نخواهد بود:
تعداد عناصر در پک تنها یک بار استنتاج میشود، اما در صورت تکرار شدن، انواع باید دقیقاً منطبق باشند:
مقداردهی پرانتزی تجمیعها
اکنون مقداردهی پرانتزی تجمیعها به همان شیوه مقداردهی آکولادی عمل میکند، به جز این که تبدیلهای کوچکسازی مجاز هستند، مقداردهی اختصاصی مجاز نیست، امکان بسط عمر مقادیر موقت وجود ندارد و حذف آکولادی نیز ممکن نیست. عناصر فاقد مقداردهنده به صورت «با مقدار» مقداردهی میشوند. به این ترتیب امکان استفاده هموار از تابعهای فکتوری مانند ()std::make_unique<>()/emplace در تجمیعها فراهم میآید.

پارامترهای تمپلیت غیر نوعی
در این بخش به بررسی خصوصیت پارامترهای قالبی غیر نوعی در C++20 میپردازیم.
انواع کلاس در پارامترهای تمپلیت غیر نوعی
پارامترهای تمپلیت غیر نوعی اکنون میتوانند انواع کلاس لفظی باشند. این انواع قابلیت استفاده به صورت یک متغیر constexpr را با همه اعضای مبنا و غیر استاتیک به صورت public و غیر mutable دارند. وهلههایی از این کلاسها به صورت اشیای const ذخیره میشوند و حتی امکان فراخوانی تابعهای عضو آنها نیز وجود دارد. یک نوع جدیدی از پارامتر تمپلیت غیر نوعی به صورت یک placeholder برای یک نوع کلاس استنتاجی وجود دارد. در مثال زیر fixed_string یک نام تمپلیت و نه یک نام نوع است، اما میتوانیم از آن برای اعلان پارامتر تمپلیت template<fixed_string S> استفاده کنیم. در چنین مواردی کامپایلر آرگومانهای پارامتر را برای fixed_string پیش از وهلهسازی از f<>() با استفاده از یک اعلان ابداعی به شکل T x = template-argument; استنتاج میکند. روش استفاده از آن برای ایجاد یک کلاس ساده رشته زمان کامپایل چنین است:
پارامترهای تمپلیت غیر نوعی تعمیمیافته
پارامترهای تمپلیت غیر نوعی به صورت انواع مشهور به ساختاری تعمیم مییابند. «نوع ساختاری» (Structural type) یکی از موارد زیر است:
- نوع اسکالر (حسابی، اشارهگر، اشارهگر به عضو، شمارشی، std::nullptr_t)
- ارجاع lvalue
- کلاس لفظی با مشخصههای زیر: همه کلاسهای مبنا و اعضای دادهای غیر استاتیک پابلیک و غیر mutable باشند و انواعشان ساختاری یا انواع آرایهای باشد.
به این ترتیب امکان استفاده از نوع اعشاری و انواع کلاس به صورت پارامترهای تمپلیت وجود دارد:
نکته قابل توجه در این خصوص آن است که آرگومانهای تمپلیت غیر نوعی نه با ()==operator خود بلکه به شیوه bitwise-like مقایسه میشوند. معنی این حرف آن است که بازنمایی بیتی آنها برای مقایسه مورد استفاده قرا میگیرد. Union-ها استثنا هستند، زیرا کامپایلر میتواند اعضای فعالشان را پیگیری کند. دو یونیون زمانی با هم برابر هستند که هر دو عضو فعال نداشته باشند و یا عضو فعال یکسانی با مقدار برابر داشته باشند.
اتصالهای ساختیافته
در این بخش به بررسی ویژگی «اتصالهای ساختیافته» (Structured bindings) در C++20 میپردازیم.
دریافت لامبدا و تصریحکنندههای کلاس ذخیرهسازی برای اتصالهای ساختیافته
اتصالهای ساختیافته مجاز به داشتن خصوصیت [[maybe_unused]] و تصریحکنندههای static و thread_local هستند. همچنین اکنون امکان دریافت آنها به صورت «با مقدار» و «با ارجاع» در لامبداها فراهم آمده است. توجه کنید که فیلدهای بیتی اتصال یافته تنها به صورت با مقدار میتوانند دریافت شوند.
آزادسازی سفارشیسازی اتصالهای ساختیافته
یکی از روشهای تجزیه یک نوع برای اتصال ساختیافته از طریق API «شبه چندتایی» (tuple-like) است. این API از سه تابع std::tuple_element، std::tuple_size و دو گزینه برای get به صورت ()<e.get<I یا get<I>(e) تشکیل یافته که اولی نسبت به دومی اولویت دارد. یعنی ()get عضو نسبت به نوع غیر عضوی ارجحیت دارد. نوعی را تصور کنید که دارای یک ()get است، اما نمیتواند تجزیه شود، زیرا کامپایلر تلاش خواهد کرد از ()get عضوی استفاده کند و پاسخ نخواهد گرفت. اکنون این مشکل به ترتیب اصلاح شده که نسخه عضوی تنها در صورتی اولویت داشته باشد که یک تمپلیت و پارامتر نخست تمپلیت به صورت پارامتر تمپلیت غیر نوعی باشند.
اجازه اتصال ساختیافته به اعضای دسترسپذیر
این قابلیت موجب میشود که اتصالهای ساختیافته نه تنها به اعضای Pubic بلکه به اعضای دسترسپذیر در ساختار اعلان اتصال ساختیافته برقرار شوند.
حلقه for مبتنی بر بازه
در این بخش با یکی دیگر از قابلیتهای جدید C++20 که با حلقههای for مرتبط است آشنا خواهیم شد.
گزارههای init برای ساخت حلقههای for مبتنی بر بازه
در C++20 حلقههای for مبتنی بر بازه نیز مانند گزاره if میتوانند گزاره if داشته باشند. این گزاره میتواند برای جلوگیری از ارجاعهای آویزان استفاده شود.
تعیین قواعدی برای آزادسازی نقطه سفارشیسازی حلقه for مبتنی بر بازه
این قابلیت همانند اصلاح نقطه سفارشیسازی اتصالهای ساختیافته است. برای چرخش روی یک بازه، حلقه for مبتنی بر بازه باید یا آزاد و یا یک تابع عضویت باشد. قواعد قدیمی به ترتیبی کار میکرد که اگر هر عضو (تابع یا متغیر) به نام begin/end پیدا میشد، کامپایلر میتوانست از تابعهای عضویت استفاده کند. این وضعیت موجب بروز مشکلاتی برای نوعهایی میشد که یک begin عضو داشتند، اما هیچ end نداشتند و یا در وضعیت برعکس بودند. اکنون تابعهای عضویت تنها در صورتی مورد استفاده قرار میگیرند که هر دو نام موجود باشند و در غیر این صورت تابعهای آزاد استفاده میشوند.
خصوصیتها
در این بخش با «خصوصیتها» (Attributes) در C++20 آشنا میشویم.
[[likely]] و [[unlikely]]
خصوصیتهای [[likely]] و [[unlikely]] سرنخی در مورد احتمال مسیر اجرایی به کامپایلر میدهند که به کامپایلر کمک میکند تا کد را به روش بهتری بهینهسازی کند. این خصوصیتها میتوانند روی گزارهها از قبیل گزاره if یا حلقهها و یا روی برچسبها مانند case/default استفاده شوند.
خصوصیت [[no_unique_address]]
خصوصیت [[no_unique_address]] میتواند روی یک عضو دادهای غیر استاتیک غیر فیلدبیتی اعمال شود تا مشخص سازد که این عضو به یک نشانی یکتا نیاز ندارد. در عمل، این خصوصیت روی یک عضو دادهای بالقوه خالی اعمال میشود و کامپایلر میتواند آن را طوری بهینهسازی کند که هیچ فضایی اشغال نسازد. چین عضوی میتواند در نشانی عضو دیگر یا کلاس مبنا شریک شود.
خصوصیت [[nodiscard]] با پیام
خصوصیت [[nodiscard]] نیز مانند [[deprecated("reason")]] میتواند یک دلیل داشته باشد.
خصوصیت [[nodiscard]] برای سازندهها
این خصوصیت به طور مشخص امکان بهکارگیری [[nodiscard]] را روی سازندهها فراهم میسازد. کامپایلرها تا پیش از C++20 الزامی به پشتیبانی از آن نداشتند.
انکودینگ کاراکتر
در این بخش با انکودینگهای مختلف C++20 آشنا میشویم.
char8_t
C++17 لفظ کاراکتری u8 را برای رشتههای UTF-8 معرفی کرد، اما نوع آن char ساده بود. عدم امکان تمایز انکودینگ بر حسب یک نوع منجر به تولید کدی میشود که باید از ترفندهای مختلفی برای مدیریت انکودینگهای مختلف بهره جست. در C++20 یک نوع char8_t برای بازنمایی کاراکترهای UTF-8 معرفی شده است. این انکودینگ همان اندازه، علامتپذیری، جهتگیری و غیره را به اندازه unsigned char دارد، اما یک نوع متمایز است و «اسم مستعار» (Alias) محسوب نمیشود.
الزامات دقیقتر یونیکد
در نسخه جدید زبان سی پلاس پلاس انواع char16_t و char32_t به صورت صریح باید به ترتیب لفظهای رشتهای UTF-16 و UTF-32 را بازنمایی کنند. نامهای سراسری کاراکتر یعنی \Unnnnnnnn و \uNNNN باید با نقاط کد ISO/IEC 10646 متناظر باشند و جایگزین نقاط کد دیگر نشوند، چون جز در این حالت برنامه شکل نامناسبی خواهد داشت.

برخی تغییرات ظاهری
مقداردهندههای اختصاصی
در نسخه جدید این زبان امکان مقداردهی اعضای تجمیع خاص (اختصاصی) و رد شدن از باقی موارد وجود دارد. برخلاف زبان C ترتیب مقداردهی باید همانند ترتیب اعلان تجمیع باشد:
مقداردهیهای اعضای پیشفرض برای فیلدهای بیتی
تا C++20 برای عرضه مقدار پیشفرض برای یک فیلد بیتی شخص باید یک سازنده پیشفرض ایجاد میکرد، اما در نسخه جدید این زبان میتوان از ساختار مقداردهی عضو پیشفرض که آسانتر است بهره گرفت.
ساخت typename با اختیار بیشتر
Typename را میتوان در ساختارهایی که هیچ چیز به جز نام نوع ظاهر نخواهد شد، حذف کرد:
فضاهای نام inline تودرتو
اکنون کلیدواژه inline مجاز به استفاده در تعاریف تودرتوی فضا نام است:
استفاده از enum
شمارشهای دامنهدار ابزار فوقالعادهای هستند. تنها مشکل آنها این است که طول زیادی دارند: my_enum::enum_value. برای نمونه در گزاره switch که همه مقادیر ممکن enum بررسی میشود، بخش ::my_enum باید برای هر برچسب تکرار شود. با استفاده از اعلان enum میتوان همه نامهای شمارشی را در دامنه جاری وارد کرد تا به صورت نامهای احراز نشده ظاهر شوند و به ای ترتیب میتوان بخش ::my_enum را نادیده گرفت. این قاعده میتواند روی شمارشهای بیدامنه و حتی روی یک شمارنده منفرد نیز اعمال شود.
استنتاج اندازه ارائه در عبارتهای new
این اصلاحیه موجب میشود که کامپایلر بتواند اندازه آرایه را در عبارتهای new درست مانند روشی که در مورد متغیرهای لوکال عمل میکند تشخیص دهد:
استنتاج آرگومان تمپلیت کلاس برای تمپلیتهای مستعار
اکنون CTAD با اسامی مستعار نوع نیز کار میکند:
Constinit
++C یک مشکل مشهور «ترتیب مقداردهی استاتیک» داشت که در زمان تعریف نشده بودن ترتیب مقداردهی متغیرهای ذخیرهسازی استاتیک از واحدهای ترجمه دیگر رخ میداد. متغیرهای دارای مقداردهی صفر/ثابت از بروی این مشکل جلوگیری میکنند زیرا در زمان کامپایل مقداردهی میشوند. اکنون constinit الزام میکند که در زمان کامپایل مقداردهی شود و برخلاف constexpr امکان استفاده از تخریبگرهای غیر trivial نیز وجود دارد. کاربرد دوم constinit در اعلانهای thread_local بدون مقداردهی است. در چنین مواردی constinit به کامپایلر اعلام میکند که متغیر قبلاً مقداردهی شده است، چون در غیر این صورت کامپایلر معمولاً کدی اضافه میکند که در صورت نیاز در هر کاربرد بررسی و مقداردهی کند.
اعداد صحیح علامتدار مکمل دو هستند
معنی این حرف آن است که اعداد صحیح علامتدار در C++20 به طور تضمینشده مکمل دو هستند. به این ترتیب برخی رفتارهای تعریف نشده و تعریف شده بر اساس پیادهسازی حذف میشوند، زیرا بازنمایی دودویی اصلاح شده است. سرریز برای اعداد صحیح علامتدار همچنان UB است اما همین حالت هم اکنون خوش-تتعریف محسوب میشود.
__VA_OPT__ برای ماکروهای متغیرپذیر
این قابلیت جدید سی پلاس پلاس امان مدیریت سادهتر «ماکروهای متغیرپذیر» (variadic macros) را فراهم ساخته است. اگر ماکرو خالی باشد به صورت __VA_ARGS__ بسط مییابد و در غیر این صورت به محتوای ماکرو بسط خواهد یافت. این قابلیت به طور خاص زمانی مفید است که ماکرو یک تابع را با برخی آرگومان (های) از پیش تعریف شده و در ادامه با __VA_ARGS__ اختیاری فرا بخواند. در چنین مواردی اگر __VA_ARGS__ خالی باشد، __VA_OPT__ امکان حذف کامای انتهایی را فراهم میسازد.
تابعهای پیشفرضی صریح با مشخصههای استثنای متفاوت
این قابلیت امکان تعیین مشخصههای استثنای یک تابع صریحاً پیشفرضی را به روشی متفاوت از مشخصههای تابع با اعلان ضمنی فراهم میسازد. تا C++20 چنین اعلانهایی موجب خروج برنامه از حالت خوش-تعریف میشدند. اکنون این وضعیت مجاز است و البته مشخصه استثنا را در به صورت عملی ارائه میکند. این حالت در مواردی مفید است که بخواهیم noexcept بودن برخی انواع عملیات را الزام کنیم. برای نمونه به جهت تضمین قوی استثنا، std::vector عناصرش را تنها در صورتی به یک فضای ذخیرهسازی جدید منتقل میکند که سازندههای جابجایی آن noexcept باشند و در غیر این صورت عناصر کپی میشوند. برخی اوقات این پیادهسازی سریعتر حتی در صورتی که عنصر در طی جابجایی حذف شوند، برای ما مطلوب خواهد بود. به طور معمول وقتی یک تابع به صورت noexcept نشانهگذاری شده باشد، ()std::terminate فراخوانی میشود.
تخریب operator delete
C++20 یک عملگر ()operator delete خاص کلاس را معرفی کرده که یک تگ اختصاصی به صورت std::destroying_delete_t میگیرد. در چنین حالتی کامپایلر تخریبگر شیء را پیش از فراخوانی ()operator delete فراخوانی نمیکند و باید به صورت دستی فراخوانی شود. این وضعیت در مواردی مفید خواهد بود که لازم باشد اعضای شیء که برای استخراج اطلاعات استفاده میشوند برای خالی شدن حافظه اشغالی، آزادسازی شوند. چنین وضعیتی برای نمونه برای استخراج اندازه معتبر و فراخوانی نسخه اندازهبندیشده delete مفید است.

سازندههای explicit شرطی
اکنون همانند noexcept(bool) یک سازنده به شکل explicit(bool) داریم که امکان ساخت/تبدیل شرطی explicit را فراهم میسازد.
ماکروهای تست قابلیت
C++20 یک مجموعه ماکروهای پیشپردازشگر برای تست قابلیتهای مختلف زبان و کتابخانه ارائه کرده است که فهرست کامل آن را میتوانید در این نشانی (+) ببینید.
تبدیل آرایه با کران مشخص به کران نامشخص
در نسخه جدید سی پلاس پلاس امکان تبدیل آرایه با کرانهای مشخص به ارجاعی به آرایه با کران نامشخص وجود دارد. قواعد تصمیمگیری Overload نیز طوری بهروزرسانی شده است که اورلود با اندازه تطبیقی بهتر از اورلود ب اندازه نامشخص یا غیر تطبیقی اجرا شود.
انتقال صریح برای اشیای لوکال ماکرو و ارجاعهای rvalue
در برخی حالتهای خاص کامپایلر مجاز به جایگزینی کپی با انتقال است. اما در ادامه مشخص شد که قواعد بیش از اندازه محدودکننده است. C++17 امکان جابجایی ارجاعهای rvalue را در گزارههای return، پارامترهای تابع در عبارت throw و شکلهای مختلف تبدیل نمیدهد. C++20 این مشکلات را رفع کرده است، اما همچنان برخی موارد باقی مانده است.
تبدیل *T به bool محدود شده است
در نسخه جدید ++C تبدیل از انواع اشارهگر یا انواع «اشارهگر به عضو» به نوع bool محدود شده است و نمیتواند در مواردی که این تبدیلها مجاز نیستد استفاده شود. nullptr در مواردی که با مقداردهی مستقیم استفاده شود مشکلی ندارد.
منسوخ شدن برخی کاربردهای volatile
در C++20 برخی کاربردهای volatile در چارچوبهای زیر منسوخ شده است.
- عملگرهای داخلی پیشوندی/پسوندی افزایشی/ کاهشی روی متغیرهای احراز شده فرّار
- کاربرد نتیجه یک انتساب به شیء احراز شده فرّار
- انتسابهای ترکیبی داخلی به شکل E1 op= E2 در حالتی که E1 احراز شده فرار باشد.
- نوع بازگشتی/پارامتر احراز شده فرار
- اعلانهای اتصال ساختیافته احراز شده فرّار
توجه کنید که در حملات فوق منظور از «احراز شده فرّار» (volatile-qualified) احراز سطح بالا است و نه هر نوع volatile در یک نوع. گاهی اوقات در مواردی مانند volatile int* px میبینیم که در عمل یک اشارهگر به in فرار است و از این رو احراز شده فرار نیست.
منسوخ شدن عملگر کاما در زیرنویسها
عملگر کاما درون زیرنویسها منسوخ شده است تا عملگر زیرنویس چند بعدی (متغیرپذیر) در آینده مجاز باشد. رویکرد کنونی به این موضوع این است که path_type سفارشی با path_type::operator, () و operator[](path_type) اورلود داشته باشیم. []operator متغیرپذیر نیاز به چنین ترفندهای پیچیده را حذف میکند.
اصلاحیهها
در این بخش برخی اصلاحیههای جزئی مطرح شده در نسخه 20 زبان برنامهنویسی سی پلاس پلاس را بررسی میکنیم. برخی از این موارد مدتها است که از سوی کامپایلر پیادهسازی شدهاند اما در استاندارد بازتاب نیافتهاند. شاید این موارد را در عمل مشاهده نکنید.
سازندههای لیست مقداردهی در استنتاج آرگومان تمپلیت کلاس
در این مثال، دو مقداردهی با ترکیب یکسان موجب ایجاد دو نوع استنتاج CATD متفاوت شدهاند. دلیل این امر آن است که std::vector دارای سازنده std::initializer_list است و آن را ترجیح میدهد . در حالی که std::tuple چنین چیزی ندارد و سازنده copy را ترجیح میدهد.
با این اصلاحیه، سازنده کپی در زمان مقداردهی از یک عنصر واحد به سازنده لیست که نوعش یک حالت خاص یا فرزند یک حالت خاص از تمپلیت کلاس در حال ساخت است ترجیح دارد.
اشارهگرهای احراز شده const& به اعضا
مشکل سابقاً این بود که استفاده از.* با rvalue دارای اشارهگر احراز شده ارجاع به تابع عضو مجاز نبود. اینک مجاز است.
سادهسازی دریافت لامبدای ضمنی
این قابلیت موجب سادهتر شدن دریافت لامبداها شده است. لامبداها درون مقداردهنده عضو پیشفرض اکنون به طور رسمی میتوانند لیست دریافت داشته باشند و دامنه محصورشان همان دامنه کلاس است.
موجودیتها به طور ضمنی دریافت میشوند، هر چند درون گزارههای حذف شده و typeid باشند:
عدم تطبیق Const با سازنده کپی پیشفرض
این اصلاحیه به نوع امکان میدهد که سازنده کپی پیشفرضی داشته باشد که حتی در صورتی که برخی از اعضایش یا کلاس مبنا سازنده کپی داشته باشد که آرگومانهایش را با ارجاع غیر const میگیرد، آرگومانها را با ارجاع const بگیرد:
struct NonConstCopyable{
NonConstCopyable() = default;
NonConstCopyable(NonConstCopyable&){}// takes by non-const reference
NonConstCopyable(NonConstCopyable&&){}
};
// std::tuple(const std::tuple& other) = default;// takes by const reference
void f(){
std::tuple<NonConstCopyable> t; // error in C++17, OK in C++20
auto t2 = t;// always an error
auto t3 = std::move(t);// OK, move-ctor is used
}
بررسی دسترسی روی specialization-ها
اینک امکان استفاده از نوع protected/private به عنوان آرگومان تمپلیت برای specialization جزئی وجود دارد و به این ترتیب امکان specialization صریح و وهلهسازی صریح وجود دارد.
ADL و تمپلیتهای تابع ناپیدا
Id احراز نشده که در ادامهاش یک < آمده باشد و بررسی نام در مورد آن چیزی پیدا نمیکند یا تابعی پیدا کند به عنوان یک نام تمپلیت برخورد میشود تا به طور بالقوه موجب اجرای بررسی وابستگی آرگومان شود.
در موارد نادری اگر ()<operator برای تابعها موجود باشد این وضعیت موجب از کار افتادن کد موجود میشود،، اما این وضعیت از سوی کمیته به صورت آسیبشناسی در نظر گرفته شده است.
تعیین زمان نیاز به تعاریف تابع constexpr برای ارزیابی ثابت
این اصلاحیه زمان نیاز به وهلهسازی از تابعهای constexpr را مشخص میسازد. این قواعد کاملاً پیچیده هستند اما در اغلب موارد کارها مطابق انتظار پیش میرود. به جای کپی کردن و چسباندن قواعد در این بخش از مقاله چند مثال ارائه شده تا موضوع روشنتر شود.
به خاطر داشته باشید که تابعهای عضو خاص تنها زمانی تعریف میشوند که مورد استفاده قرار گیرند. با اصطلاحات C++17، سازنده جابجایی در اینجا استفاده نمیشود و لذا تعریف نشده و از این جهت برنامه خوش-تعریف نیست. اما اگر خط شماره 1 را از کامنت خارج کنیم، سازنده move مورد استفاده قرار میگیرد و لذا تعریف شده است و برنامه حالت مناسبی مییابد. این کار معنای خاصی ندارد و از این رو قواعد برای لحاظ کردن این مشکل تغییر یافتهاند.
مثال دیگر به صورت زیر است:
در مثال فوق، تابع تمپلیت constexpr را داریم که به طور بالقوه با نوع int وهلهسازی شده و باید منجر به بروز خطا شود، زیرا int::value نادرست است. در ادامه دو تابع هستند که از B? f<int>(): 0 استفاده میکنند و B همواره false است و از این رو ()<f<int هرگز لازم نخواهد بود. سؤال این است که آیا <f<int باید در اینجا وهلهسازی شود؟
قواعد جدید آنچه برای ارزیابی ثابت نیاز است را مشخص کردهاند. متغیرهای تمپلیت یا تابعها در چنین عبارتهایی باید همواره وهلهسازی شوند هر چند نیازی به ارزیابی عیارت نباشد. یکی از این موارد لیست مقداردهی آکولادی است، از این رو وهلهسازی در عبارت <int{B? f<T>(): 0} f<T همواره موجب بروز خطا خواهد شد.
ایجاد ضمنی اشیا برای دستکاری سطح پایین شیء
در C++17 یک شیء میتواند با تعریف با عبارت new یا با تغییر دادن عضو فعال یک union ایجاد شود. اینک مثال زیر را در نظر بگیرید:
با این ک این وضعیت طبیعی به نظر میرسد، اما این کد در C++17 رفتار تعریف نشدهای دارد زیرا x بر اساس قواعد زبان ایجاد نشده و نوشتن در عضو یک موجودیت غیر موجود UB است. قواعد چنین وضعیتی با تعیین نوعهایی که میتوانند به طور ضمنی ایجاد شوند و عملیاتی که میتوانند این اشیا را به صورت ضمنی ایجاد کنند روشنسازی شده است. انواعی که میتوانند به طور ضمنی ایجاد شوند به شرح زیر هستند:
- انواع اسکالر
- انواع تجمیع
- انواع کلاس با هر سازنده مشروع بدیهی و تخریبگر بدیهی
عملیاتی که میتوانند اشیای دارای طول عمر ضمنی را به طور ضمنی ایجاد کنند به شرح زیر هستند:
- عملیاتی که با طول عمر یک ارائه از نوع char, unsigned char, std::byte آغاز میشوند.
- عملگر new و عملگر []new
- std::allocator<T>::allocate(std::size_t n)
- تابعهای تخصیص کتابخانه C به صورت aligned_alloc, calloc, malloc, و realloc
- memcpy و memmove
- std::bit_cast
همچنین قاعده برای شبه تخریبگر (تخریبگر برای انواع داخلی) تغییر یافته است. تا قبل از C++20 این تخریبگر هیچ تأثیری نداشت اما اکنون طول عمر شیء را پایان میبخشد.
سخن پایانی
به این ترتیب با پایان این مقاله طولانی در مورد قابلیتهای جدید زبان ++C در نسخه 20 میرسیم. البته این مطلب برای معرفی همه تغییرات و قابلیتهای جدید ++C به هیچ وجه جامع نیست، اما به عنوان یک مرجع برای بررسی و آشنای با این موارد مناسب خواهد بود.












