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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ