ข้ามไปที่เนื้อหา

Ch16: Concepts

TL;DR: Template Constraints: Concepts (C++20) ช่วยกำหนดเงื่อนไขจำกัดพารามิเตอร์ของ Template ในระดับฟังก์ชันลายเซ็นโดยตรง ทำให้อ่านง่ายขึ้นแทนการใช้ Type Traits และ static_assert แบบเก่า

⚡ Quick Reference

#include <iostream>
#include <concepts>
#include <type_traits>

// ประกาศ Custom Concept โดยใช้งาน Type Traits สำเร็จรูป
template <typename T>
concept MyConcept = std::is_integral_v<T>;

// ประกาศ Custom Concept โดยระบุตรรกะในบล็อก requires
template <typename T>
concept Addable = requires(T a, T b) {
    a + b; // Simple requirement: ตรวจสอบความถูกต้องทางไวยากรณ์
};

// การใช้ Nested และ Compound Requirement ภายใน Concept
template <typename T>
concept TinyAndConvertible = requires(T a) {
    requires sizeof(T) <= 4; // Nested requirement: บังคับให้ตรวจสอบเงื่อนไขบูลีนเป็นจริง
    { a + 1 } -> std::convertible_to<int>; // Compound requirement: ตรวจไวยากรณ์พร้อมเช็คชนิดผลลัพธ์แปลงข้ามได้
};

// ไวยากรณ์รูปแบบที่ 1: กำกับข้อจำกัดต่อท้ายบรรทัดประกาศเทมเพลต
template <typename T>
requires std::integral<T>
T add1(T a, T b) { return a + b; }

// ไวยากรณ์รูปแบบที่ 2: เขียนชื่อ Concept แทนคำว่า typename ในพารามิเตอร์เทมเพลต
template <std::integral T>
T add2(T a, T b) { return a + b; }

// ไวยากรณ์รูปแบบที่ 3: บล็อกพารามิเตอร์แบบ auto ด้วยเงื่อนไขของ Concept
auto add3(std::integral auto a, std::integral auto b) { return a + b; }

// ไวยากรณ์รูปแบบที่ 4: เขียนข้อกำหนดดักต่อท้ายพารามิเตอร์ของฟังก์ชัน
template <typename T>
T add4(T a, T b) requires std::integral<T> { return a + b; }

// นำเสนอเงื่อนไขร่วมกันโดยผ่านตัวดำเนินการ OR / AND
template <typename T>
requires std::integral<T> || std::floating_point<T>
void print_num(T val) {
    // ดักควบคุมออบเจกต์จำพวก auto ให้เป็นไปตามเงื่อนไขที่กำหนด
    std::integral auto copy = val;
}

int main() {
    return 0;
}

🧠 Core Concepts

  • Template Constraints: Concepts (C++20) ช่วยกำหนดเงื่อนไขจำกัดพารามิเตอร์ของ Template ในระดับฟังก์ชันลายเซ็นโดยตรง ทำให้อ่านง่ายขึ้นแทนการใช้ Type Traits และ static_assert แบบเก่า
  • Requirements Clauses: Custom Concept สามารถตั้งเงื่อนไขได้หลายรูปแบบ ได้แก่ Simple (ตรวจไวยากรณ์), Nested (ตรวจผลตรรกะ boolean), และ Compound (ตรวจไวยากรณ์พร้อมตรวจสอบประเภทของผลลัพธ์) ภายใต้คำสั่ง requires
  • Flexible Syntaxes: เราสามารถนำเสนอเงื่อนไข Concept ได้ในหลายตำแหน่ง เช่น เขียนแทน typename ในรายการพารามิเตอร์, เขียนต่อท้ายฟังก์ชันเป็น requires clause, หรือใส่หน้าพารามิเตอร์ที่เป็น auto

⚠️ Pitfalls (Quick Scan)

ข้อผิดพลาด วิธีแก้
การใช้เงื่อนไขตรวจสอบธรรมดา (Simple Requirement เช่น sizeof(T) <= 4) เพื่อวัดค่าความจริง ครอบเงื่อนไขตรวจสอบด้วยคำสั่ง requires ซ้อนด้านใน (Nested Requirement) เพื่อบังคับเช็คค่าจริง
ลืมไปว่าการบวกตัวแปรชนิดเล็ก (เช่น char) จะกระตุ้นการขยายเป็น int อัตโนมัติ พิมพ์ค่า Return แบบกำหนดประเภทตายตัว หรือใช้การแปลงชนิดข้อมูลช่วยปรับค่าขาส่งคืน
เข้าใจผิดว่าการตรวจสอบไวยากรณ์คำสั่ง (เช่น a b) จะช่วยการันตีความหมายทางตรรกะ ออกแบบ Concept ควบคู่กับการใช้ Type Traits เพื่อจำกัดชนิดข้อมูลให้ปลอดภัยยิ่งขึ้น
เขียนนิยามบล็อก Concept ส่วนตัวซ้อนกันอยู่ในบรรทัดเดียว (Inline Concept) แยกเขียนโครงสร้าง Concept แยกบรรทัดกันภายนอก แล้วเรียกใช้งานผ่านชื่ออ้างอิงแทน
พึ่งพาการแสดงผลขีดเส้นใต้เตือนปัญหาของตัวแก้ไขโค้ด (IDE) เพียงอย่างเดียว ตรวจสอบข้อความแจ้งเตือนจากการสร้างระบบโดย Compiler อย่าง GCC เพื่อความชัดเจน

📖 Full Details

Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู) * **การใช้เงื่อนไขตรวจสอบธรรมดา (Simple Requirement เช่น `sizeof(T) <= 4`) เพื่อวัดค่าความจริง** -> **Compiler ตรวจสอบแค่ว่าไวยากรณ์โค้ดถูกต้องไหม ทำให้ยอมรับประเภทข้อมูลที่มีขนาดใหญ่กว่าเกณฑ์ส่งเข้ามาได้** -> **ครอบเงื่อนไขตรวจสอบด้วยคำสั่ง `requires` ซ้อนด้านใน (Nested Requirement) เพื่อบังคับเช็คค่าจริง (13:58)** * **ลืมไปว่าการบวกตัวแปรชนิดเล็ก (เช่น `char`) จะกระตุ้นการขยายเป็น `int` อัตโนมัติ** -> **ผลลัพธ์ขาส่งคืน (Return type) อาจหลุดข้อจำกัดขนาดของ Concept ที่ตั้งไว้** -> **พิมพ์ค่า Return แบบกำหนดประเภทตายตัว หรือใช้การแปลงชนิดข้อมูลช่วยปรับค่าขาส่งคืน (18:50)** * **เข้าใจผิดว่าการตรวจสอบไวยากรณ์คำสั่ง (เช่น `a * b`) จะช่วยการันตีความหมายทางตรรกะ** -> **Compiler ยอมรันโค้ดหากไวยากรณ์ผ่าน แม้ว่าชนิดข้อมูลนั้นจะนำมาคูณกันแล้วไม่ได้ตรรกะที่สมเหตุสมผล** -> **ออกแบบ Concept ควบคู่กับการใช้ Type Traits เพื่อจำกัดชนิดข้อมูลให้ปลอดภัยยิ่งขึ้น (09:35)** * **เขียนนิยามบล็อก Concept ส่วนตัวซ้อนกันอยู่ในบรรทัดเดียว (Inline Concept)** -> **ทำให้ลายเซ็นของฟังก์ชันมีความรกรุงรัง อ่านและทำความเข้าใจได้ยากมาก** -> **แยกเขียนโครงสร้าง Concept แยกบรรทัดกันภายนอก แล้วเรียกใช้งานผ่านชื่ออ้างอิงแทน (26:10)** * **พึ่งพาการแสดงผลขีดเส้นใต้เตือนปัญหาของตัวแก้ไขโค้ด (IDE) เพียงอย่างเดียว** -> **คำอธิบายแจ้งเตือนของ IDE มักจะกำกวมและระบุส่วนย่อยที่แครชได้ไม่เจาะจง** -> **ตรวจสอบข้อความแจ้งเตือนจากการสร้างระบบโดย Compiler อย่าง GCC เพื่อความชัดเจน (05:30)**

📎 Repo Files

  • 25.02UsingConcepts/main.cpp
  • 25.03BuildingYourOwnConcepts/main.cpp
  • 25.04ZoomingInOnRequiresClause/main.cpp
  • 25.05CombiningConcepts/main.cpp
  • 25.06ConceptsAndAuto/main.cpp