לדלג לתוכן

2.1 - תבניות והפקת קוד - פתרון

פתרון - הגדרה נגישה או התמחות שנוצרה במפורש

בפתרון הראשון הקובץ twice.hpp מכיל את כל ההגדרה:

#pragma once

template<class T>
T twice(T value) {
    return value + value;
}

הקובץ main.cpp יכול להשתמש גם ב־int וגם ב־double, מפני שהמהדר רואה את הגוף בזמן יצירת ההתמחויות. אין צורך בקובץ twice.cpp נוסף עבור התבנית הזאת.

בפתרון השני ה־header נשאר עם declaration, ובסוף twice.cpp מוסיפים:

template int twice<int>(int);
template double twice<double>(double);

כל שורה מבקשת מימוש מסוים. אם שורת double חסרה והגוף אינו נגיש לקורא, הכרזה על הקריאה אינה יוצרת את הקוד החסר. סוג הכשל יכול להיות בשלב הלינקוג, ולכן חשוב לקרוא איזה כלי דיווח עליו.

התמחות למערך

הקוד הבא הוא תוכנית מלאה:

#include <cstddef>
#include <cstdio>

template<class T>
struct Category {
    static const char* label() { return "value"; }
};

template<class T>
struct Category<T*> {
    static const char* label() { return "pointer"; }
};

template<class T, std::size_t N>
struct Category<T[N]> {
    static const char* label() { return "array"; }
    static constexpr std::size_t size = N;
};

int main() {
    std::puts(Category<int>::label());
    std::puts(Category<int*>::label());
    std::puts(Category<int[3]>::label());
    static_assert(Category<int[3]>::size == 3);
}

הטיפוס int[3] שונה מ־int*. בניסוי הזה מציינים את הטיפוס ישירות, ולכן המערך אינו עובר התאמה לפרמטר פונקציה שמקבל pointer. זאת הבחנה חשובה כשבהמשך נכתוב פונקציות שמנסות להסיק אורך מערך.

מה ההסקה מבטיחה

ב־smaller(1, 2) מוסק int. ב־smaller(1.0, 2.0) מוסק double. בקריאה המעורבת אין T יחיד שההסקה מקבלת משני הפרמטרים לפי החתימה הפשוטה.

ב־smaller(1, 2.0) אנחנו בוחרים את T, והמספר הראשון מותאם ל־double. אין להסיק מכאן שכל שילוב טיפוסים רצוי: למשל המרה מטיפוס גדול לטיפוס קטן יכולה לשנות ערך.

הפתרון מסביר קודם את חוזה ההסקה ורק אחר כך את המימוש שהמהדר מפיק. שינוי שמעלים שגיאת קומפילציה בלי להבין איזו המרה נוספה אינו בדיקה מספקת של קוד גנרי.