1. Vấn đề nó giải quyết
Một overload template có thể bị loại vì kiểu đối số không phù hợp mà các overload khác vẫn được xét. SFINAE cho phép lỗi thay thế kiểu trong phạm vi được quy định trở thành thất bại suy luận, thay vì lập tức làm hỏng cả chương trình.
2. Kiến thức cần có
Biết function template, overload và suy luận tham số template.
Biết class template, chuyên biệt hóa tường minh và điều kiện kiểm tra kiểu như
std::is_integral_v<T>.
3. Ý tưởng cốt lõi
SFINAE viết tắt của Substitution Failure Is Not An Error. Điểm giới hạn là ngữ cảnh trực tiếp (immediate context), chẳng hạn kiểu tham số, kiểu trả về hoặc khai báo tham số template đang được thay thế. Lỗi trong thân hàm hoặc bên trong một template khác bị khởi tạo không tự được bỏ qua. Xem quy tắc suy luận template.
SFINAE có thể lọc overload và giúp chọn chuyên biệt hóa từng phần của class template. Chuyên biệt hóa tường minh như template<> struct TypeName<int> lại cung cấp định nghĩa cho một bộ đối số cụ thể; đó không phải cùng một cơ chế.
4. Cú pháp tối thiểu
template<class T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
T twice(T value) {
return value + value;
}Cần <type_traits>. Nếu T là kiểu số nguyên, enable_if_t có kiểu int; nếu không, kiểu đó không tồn tại và ứng viên bị loại khi thay thế tham số.
5. Cách nó hoạt động
Gọi
twice(21)suy luậnT = int, điều kiện đúng, kết quả là42.Gọi
twice(2.5)suy luậnT = double; ứng viên trên bị loại. Nếu không có overload khác dùng được, lời gọi vẫn gây lỗi biên dịch.Nếu chỉ đặt phép dùng thành viên không hợp lệ trong thân hàm, việc chọn overload đã xong trước khi thân hàm được khởi tạo. SFINAE không cứu lỗi đó.
Với class template, một mẫu như
Trait<T, std::void_t<typename T::value_type>>có thể khớp khiT::value_typetồn tại; cần có template chính để dùng khi không khớp.Function template không hỗ trợ chuyên biệt hóa từng phần. Dùng overload hoặc ràng buộc C++20 khi cần chọn theo điều kiện.
6. Lỗi thường gặp
Hiểu SFINAE là “mọi lỗi template đều được bỏ qua”.
Tưởng ứng viên bị loại nghĩa là lời gọi luôn hợp lệ; vẫn cần một hàm phù hợp còn lại.
Nhầm chuyên biệt hóa tường minh với overload có điều kiện.
Cho rằng kiểm tra
is_integralbảo đảmvalue + valuekhông tràn. Với số nguyên có dấu, đầu vào vẫn phải giữ tổng trong miền biểu diễn.
7. Khi nào nên dùng
SFINAE hữu ích khi đọc hoặc duy trì thư viện trước C++20, nhất là mã phát hiện thành viên và overload có điều kiện. Với C++20, requires và concept thường diễn đạt điều kiện sử dụng rõ hơn; ràng buộc kiểu vẫn không thay thế điều kiện hợp lệ của giá trị lúc chạy.
8. Ví dụ đơn giản
Mã mẫu C++20 còn có một chuyên biệt hóa tường minh:
template<class T>
struct TypeName {
static constexpr const char* value = "other";
};
template<>
struct TypeName<int> {
static constexpr const char* value = "int";
};Chương trình in 42 int: twice(21) dùng SFINAE để tham gia overload, còn TypeName<int> chọn định nghĩa chuyên biệt. Hai cơ chế xuất hiện cạnh nhau nhưng có vai trò khác nhau.
Mã mẫu hoàn chỉnh
Tệp mã nguồn
dailycppinterview/176_sfinae-and-template-specialization/main.cpp
// Real-World C++ Interviews Q176: What is SFINAE? How does it relate to template
// specialization?
// Key: SFINAE means Substitution Failure Is Not An Error: when substitution fails in the
// immediate context of a function-template candidate, that candidate is removed from overload
// resolution instead of making the program ill-formed. It can enable overloads or partial class
// specializations conditionally, but it is not itself explicit specialization, and function
// templates cannot be partially specialized. In C++20, constraints and concepts usually express
// the same intent more clearly and produce better diagnostics.
#include <iostream>
#include <type_traits>
template<class T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
T twice(T value) {
return value + value;
}
template<class T>
struct TypeName {
static constexpr const char* value = "other";
};
template<>
struct TypeName<int> {
static constexpr const char* value = "int";
};
int main() {
std::cout << twice(21) << ' ' << TypeName<int>::value << std::endl;
}
9. Điều cần nhớ
Khi phân tích SFINAE, chỉ ra chính xác phép thay thế nào thất bại và lỗi nằm ở đâu. Sau đó phân biệt việc loại ứng viên, chọn chuyên biệt hóa từng phần và dùng chuyên biệt hóa tường minh.
10. Câu hỏi tự kiểm tra
Đề đầy đủ: SFINAE là gì và liên quan thế nào tới template specialization?
Vì sao twice(2.5) vẫn báo lỗi nếu chỉ có một overload? Nếu lỗi nằm trong thân twice, SFINAE có áp dụng không? Có thể viết chuyên biệt hóa từng phần cho function template không?