Skip to content

Commit 354bc7d

Browse files
committed
book: add SFINAE and std::enable_if section
Add a 2.5 subsection explaining SFINAE (with std::enable_if and expression SFINAE via decltype) as the pre-C++20 way to constrain templates, and point forward to C++20 concepts as the modern replacement. Compiled example code/2/2.28. Resolves #150
1 parent 44fa6da commit 354bc7d

3 files changed

Lines changed: 105 additions & 0 deletions

File tree

book/en-us/02-usability.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,38 @@ int main() {
927927
}
928928
```
929929

930+
### SFINAE and `std::enable_if`
931+
932+
SFINAE stands for "Substitution Failure Is Not An Error". It describes the rule that, when substituting template arguments produces an invalid type or expression in the **immediate context**, the compiler does not raise an error but silently removes that candidate from the overload set. This was the main way to constrain template parameters before C++20's concepts.
933+
934+
The most common tool is `std::enable_if` from `<type_traits>`. The `describe` below is visible only for integral types:
935+
936+
```cpp
937+
#include <type_traits>
938+
939+
template <typename T,
940+
typename = std::enable_if_t<std::is_integral_v<T>>>
941+
void describe(T) {
942+
std::cout << "integral" << std::endl;
943+
}
944+
945+
describe(42); // OK
946+
// describe(3.14); // compile error: floating point does not satisfy the constraint
947+
```
948+
949+
Another common form is **expression SFINAE**, which uses `decltype` to probe whether an expression is valid, thereby detecting at compile time whether a type has a certain capability:
950+
951+
```cpp
952+
// participates only if c.size() is a valid expression
953+
template <typename T>
954+
auto has_size(const T& c) -> decltype(c.size(), std::true_type{}) {
955+
return std::true_type{};
956+
}
957+
std::false_type has_size(...) { return std::false_type{}; }
958+
```
959+
960+
SFINAE is powerful but obscure to write and produces verbose error messages. C++20's [concepts](./10-cpp20.md#concept-and-constraints) were introduced precisely to express such constraints in a more intuitive and readable way, and can be regarded as the modern replacement for SFINAE.
961+
930962
## 2.6 Object-oriented
931963

932964
### Delegate constructor

book/zh-cn/02-usability.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,38 @@ int main() {
833833
}
834834
```
835835

836+
### SFINAE 与 `std::enable_if`
837+
838+
SFINAE 是 “Substitution Failure Is Not An Error”(替换失败并非错误)的缩写。它描述的是这样一条规则:在对模板参数进行替换时,如果在**直接上下文 (immediate context)** 中产生了非法的类型或表达式,编译器并不会报错,而是悄悄地把这个候选从重载集合中剔除。这正是在 C++20 的 concept 出现之前,对模板参数进行约束的主要手段。
839+
840+
最常见的工具是 `<type_traits>` 中的 `std::enable_if`。下面的 `describe` 只对整型可见:
841+
842+
```cpp
843+
#include <type_traits>
844+
845+
template <typename T,
846+
typename = std::enable_if_t<std::is_integral_v<T>>>
847+
void describe(T) {
848+
std::cout << "integral" << std::endl;
849+
}
850+
851+
describe(42); // 正确
852+
// describe(3.14); // 编译错误:浮点类型不满足约束,该重载被剔除
853+
```
854+
855+
另一种常见形式是**表达式 SFINAE**,它借助 `decltype` 来探测某个表达式是否合法,从而在编译期检测类型是否具备某种能力:
856+
857+
```cpp
858+
// 当且仅当 c.size() 是合法表达式时,这个重载才会参与候选
859+
template <typename T>
860+
auto has_size(const T& c) -> decltype(c.size(), std::true_type{}) {
861+
return std::true_type{};
862+
}
863+
std::false_type has_size(...) { return std::false_type{}; }
864+
```
865+
866+
SFINAE 虽然强大,但写法晦涩、错误信息冗长。C++20 的 [概念 (concept)](./10-cpp20.md#概念与约束) 正是为了以更直观、可读的方式表达这类约束而引入的,可以将其视为 SFINAE 的现代替代品。
867+
836868
## 2.6 面向对象
837869

838870
### 委托构造

code/2/2.28.sfinae.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// 2.28.sfinae.cpp
3+
// chapter 2 language usability
4+
// modern cpp tutorial
5+
//
6+
// created by changkun at changkun.de
7+
// https://github.com/changkun/modern-cpp-tutorial
8+
//
9+
10+
#include <iostream>
11+
#include <type_traits>
12+
#include <vector>
13+
14+
// SFINAE ("Substitution Failure Is Not An Error"): when substituting
15+
// template arguments produces an invalid type/expression in the
16+
// immediate context, that overload is silently removed from the
17+
// candidate set instead of causing a hard error.
18+
19+
// 1) Constrain with std::enable_if: this overload exists only for
20+
// integral types.
21+
template <typename T,
22+
typename = std::enable_if_t<std::is_integral_v<T>>>
23+
void describe(T) {
24+
std::cout << "integral" << std::endl;
25+
}
26+
27+
// 2) Expression SFINAE: this overload participates only if `c.size()`
28+
// is a valid expression.
29+
template <typename T>
30+
auto has_size(const T& c) -> decltype(c.size(), std::true_type{}) {
31+
return std::true_type{};
32+
}
33+
std::false_type has_size(...) { return std::false_type{}; }
34+
35+
int main() {
36+
describe(42); // integral
37+
38+
std::cout << std::boolalpha;
39+
std::cout << has_size(std::vector<int>{1, 2, 3}) << std::endl; // true
40+
std::cout << has_size(42) << std::endl; // false
41+
}

0 commit comments

Comments
 (0)