2.2 - אילוצים וממשקים גנריים - פתרון
פתרון - חוקיות קריאה מול משמעות¶
התוכנית הבאה מציגה שתי דרגות של דרישה:
#include <concepts>
#include <cstdio>
template<class T>
concept Resettable = requires(T& value) {
{ value.reset() } -> std::same_as<void>;
};
template<class T>
concept NothrowResettable = requires(T& value) {
{ value.reset() } noexcept -> std::same_as<void>;
};
struct Good {
void reset() noexcept {}
};
struct MayThrow {
void reset() {}
};
struct WrongResult {
int reset() { return 0; }
};
template<Resettable T>
void clear(T& value) {
value.reset();
}
int main() {
static_assert(Resettable<Good>);
static_assert(NothrowResettable<Good>);
static_assert(Resettable<MayThrow>);
static_assert(!NothrowResettable<MayThrow>);
static_assert(!Resettable<WrongResult>);
Good value;
clear(value);
}
הבדיקה של noexcept מתייחסת לחוזה הביטוי. היא אינה מריצה אותו כדי לראות אם במקרה נזרקה חריגה. פעולת cleanup בתוך destructor עשויה לדרוש חוזה אי־זריקה, אבל פונקציה שמסוגלת לדווח ולהתאושש אינה בהכרח צריכה את אותה מגבלה.
הconcept אינו מוכיח שהאובייקט נעשה ריק, שמונה אופס או שמשאב נסגר. אפשר לכתוב reset בעלת חתימה מתאימה שלא עושה דבר. הדרישה הסמנטית צריכה להופיע בחוזה ולהיבדק על המימוש.
הדפסה לפי סדר¶
הקוד הבא הוא קטע מלא שניתן לבנות בנפרד:
#include <cstdio>
template<class... T>
void print_values(T... values) {
((std::printf("%d ", static_cast<int>(values))), ...);
std::puts("");
}
int main() {
print_values();
print_values(1, 2, 3);
}
האופרטור הפסיק נותן sequencing בין האופרנדים, ולכן הדפסות החבילה הזאת מתבצעות בסדר המתוכנן. ה־fold על חבילה ריקה של פסיק תקין ולא מדפיס איבר. הממשק כאן מתאים רק לערכים שהמרתם ל־int מוצדקת; ספרייה ציבורית צריכה להוסיף constraint או לבחור formatting שמתאים לטיפוסים הרצויים.
השוואה עם מעגל¶
אפשר להגדיר יחס שבו אפס קטן מאחד, אחד קטן משתיים ושתיים קטן מאפס. כל קריאה מחזירה bool, ולכן דרישה שבודקת רק חוקיות של פחות־מ־יכולה לעבור.
היחס אינו טרנזיטיבי, ולכן אינו strict weak ordering הנדרש מהשוואות מסוימות בספרייה. ההבחנה החשובה היא בין היכולת לבצע את הפעולה לבין החוקים שהאלגוריתם מסתמך עליהם לאורך רצף פעולות.