0Pricing
C++ Academy · บทเรียน

SFINAE

เปิดใช้ฟังก์ชันตามเงื่อนไข

SFINAE เป็นบทเรียน C++ Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C++ Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C++ Academy มีบทเรียนทั้งหมด 4 บทเรียน

SFINAE หมายถึงอะไร

SFINAE ย่อมาจาก "การแทนที่ล้มเหลวไม่ใช่ข้อผิดพลาด" เมื่อคอมไพเลอร์แทนที่อาร์กิวเมนต์แม่แบบแล้วผลลัพธ์ไม่ถูกต้องตามรูปแบบ ผู้สมัครนั้นจะถูกนำออกแทนที่จะทำให้เกิดข้อผิดพลาดร้ายแรง

  • ช่วยเปิดใช้การโอเวอร์โหลดแบบมีเงื่อนไข
  • เป็นพื้นฐานของการเลือกขณะคอมไพล์
#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
isOdd(T n) { return n % 2 != 0; }

int main() {
    std::cout << std::boolalpha << isOdd(3) << '\n';
    return 0;
}

การแทนที่ล้มเหลวในการทำงานจริง

หากการแทนที่ชนิดทำให้ลายเซ็นของฟังก์ชันไม่ถูกต้อง คอมไพเลอร์จะข้ามฟังก์ชันนั้นอย่างเงียบ ๆ และตรวจสอบผู้สมัครรายการอื่นต่อไป

#include <iostream>

template <typename T>
auto sizeOfValue(T t) -> decltype(t.size()) { return t.size(); }

int main() {
    std::string s = "hello";
    std::cout << sizeOfValue(s) << '\n';
    return 0;
}

โอเวอร์โหลดสองรายการ มีรายการหนึ่งชนะ

จัดเตรียมโอเวอร์โหลดสองรายการ โดยให้มีเพียงรายการเดียวที่แทนที่สำเร็จสำหรับชนิดที่กำหนด SFINAE จะตัดรายการที่ไม่ถูกต้องทิ้ง

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, const char*>::type
name() { return "integral"; }

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, const char*>::type
name() { return "floating"; }

int main() {
    std::cout << name<int>() << ' ' << name<double>() << '\n';
    return 0;
}

decltype และ SFINAE จากนิพจน์

การใช้ decltype กับนิพจน์ในชนิดผลลัพธ์ ทำให้ฟังก์ชันมีอยู่เฉพาะเมื่อนิพจน์นั้นถูกต้องสำหรับชนิดดังกล่าว

#include <iostream>

template <typename T>
auto tryDouble(T x) -> decltype(x + x) { return x + x; }

int main() {
    std::cout << tryDouble(21) << '\n';
    std::cout << tryDouble(2.5) << '\n';
    return 0;
}

การตรวจจับสมาชิก

เทคนิค SFINAE แบบคลาสสิกจะตรวจว่าชนิดมีสมาชิกเฉพาะหรือไม่ โดยตรวจสอบว่านิพจน์ที่ใช้สมาชิกนั้นสามารถคอมไพล์ได้หรือไม่

#include <iostream>
#include <type_traits>
#include <vector>

template <typename T>
auto hasSize(int) -> decltype(std::declval<T>().size(), std::true_type{});

template <typename T>
std::false_type hasSize(...);

int main() {
    std::cout << std::boolalpha;
    std::cout << decltype(hasSize<std::vector<int>>(0))::value << '\n';
    std::cout << decltype(hasSize<int>(0))::value << '\n';
    return 0;
}

ทางเลือกสำรอง ...

โอเวอร์โหลดที่รับ ... (อาร์กิวเมนต์จำนวนมาก) มีลำดับความสำคัญต่ำที่สุด โอเวอร์โหลดนี้ทำหน้าที่เป็นทางเลือกสำรองเมื่อโอเวอร์โหลดที่มีข้อจำกัดด้วย SFINAE ล้มเหลว

#include <iostream>

template <typename T>
auto pick(int) -> decltype(T{}.value, void()) { std::cout << "has value\n"; }

template <typename T>
void pick(...) { std::cout << "no value\n"; }

struct WithValue { int value = 0; };

int main() {
    pick<WithValue>(0);
    pick<int>(0);
    return 0;
}

void_t

std::void_t ของ C++17 ช่วยให้การตรวจจับง่ายขึ้น โดยแมปชนิดที่มีรูปแบบถูกต้องใด ๆ ไปเป็น void ดังนั้นการทำให้จำเพาะจะทำงานก็ต่อเมื่อนิพจน์ที่ตรวจสอบนั้นถูกต้อง

#include <iostream>
#include <type_traits>
#include <vector>

template <typename T, typename = void>
struct HasSize : std::false_type {};

template <typename T>
struct HasSize<T, std::void_t<decltype(std::declval<T>().size())>> : std::true_type {};

int main() {
    std::cout << std::boolalpha;
    std::cout << HasSize<std::vector<int>>::value << '\n';
    std::cout << HasSize<int>::value << '\n';
    return 0;
}

ทำไมไม่ปล่อยให้เกิดข้อผิดพลาดไปเลย

หากไม่มี SFINAE การแทนที่ที่ไม่ถูกต้องจะเป็นข้อผิดพลาดร้ายแรงขณะคอมไพล์และไม่สามารถกู้คืนได้ SFINAE เปลี่ยนข้อผิดพลาดนั้นให้เป็นสถานะ "ไม่ใช่ผู้สมัคร" อย่างเงียบ ๆ

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_pointer<T>::value, bool>::type
isNull(T p) { return p == nullptr; }

int main() {
    int x = 0;
    std::cout << std::boolalpha << isNull(&x) << '\n';
    return 0;
}

ทางเลือกแบบการส่งแท็ก

SFINAE อาจอ่านได้ยาก การส่งแท็กจะเลือกการนำไปใช้โดยใช้ชนิดแท็กขนาดเล็กและการแก้ไขการโอเวอร์โหลดแทน

#include <iostream>
#include <type_traits>

template <typename T>
void impl(T v, std::true_type) { std::cout << "integral: " << v << '\n'; }

template <typename T>
void impl(T v, std::false_type) { std::cout << "other: " << v << '\n'; }

template <typename T>
void handle(T v) { impl(v, std::is_integral<T>{}); }

int main() {
    handle(5);
    handle(2.5);
    return 0;
}

SFINAE และ constexpr if

ใน C++17 if constexpr มักใช้แทน SFINAE สำหรับการแยกแขนงภายในฟังก์ชันเดียว เนื่องจากแขนงที่ไม่ได้ใช้จะถูกตัดทิ้งขณะคอมไพล์

#include <iostream>
#include <type_traits>

template <typename T>
void describe(T v) {
    if constexpr (std::is_integral<T>::value) {
        std::cout << "integral: " << v << '\n';
    } else {
        std::cout << "other: " << v << '\n';
    }
}

int main() {
    describe(7);
    describe(3.14);
    return 0;
}

นำทุกอย่างมาประกอบกัน

SFINAE มีประโยชน์อย่างยิ่งเมื่อต้องเลือกระหว่างแม่แบบฟังก์ชันแยกกันตามคุณสมบัติของชนิดในระดับการโอเวอร์โหลด

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_signed<T>::value, T>::type
absValue(T v) { return v < 0 ? -v : v; }

int main() {
    std::cout << absValue(-9) << '\n';
    return 0;
}

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจของคุณเกี่ยวกับ SFINAE

สรุปทบทวน

คุณได้เรียนรู้เกี่ยวกับ SFINAE ดังนี้:

  • การแทนที่ที่ไม่ถูกต้องจะนำผู้สมัครออกแทนที่จะทำให้เกิดข้อผิดพลาด
  • ใช้ร่วมกับ enable_if, decltype และ void_t เพื่อเปิดใช้ฟังก์ชันตามเงื่อนไข
  • โอเวอร์โหลด ... ทำหน้าที่เป็นทางเลือกสำรอง
  • ทางเลือกสมัยใหม่ ได้แก่ การส่งแท็กและ if constexpr

ถัดไป คุณจะมุ่งเน้นรูปแบบคลาสสิกของ enable_if โดยละเอียด

คำถามที่พบบ่อย

บทเรียน “SFINAE” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “SFINAE” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C++ Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C++ Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “SFINAE”

เปิดใช้ฟังก์ชันตามเงื่อนไข คุณปฏิบัติ C++ Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C++ Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C++ Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “SFINAE” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C++ Academy นี้ได้ไหม

ได้ บทเรียน C++ Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การทำให้เฉพาะทางแบบเต็ม
  2. การทำให้เฉพาะทางบางส่วน
  3. SFINAE
  4. รูปแบบ enable_if
← กลับไปที่ C++ Academy