SFINAE
替换失败非错(substitution failure is not an error)
比如下面这段,会编译失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct Bar {
typedef double internalType;
};
template <typename T>
typename T::internalType foo(const T& t) {
cout << "foo<T>\n";
return 0;
}
int main() {
foo(Bar());
foo(0); // << error!
}
|
编译器执行过程
- 名称查找
- 模版参数推断,这里找不到不会抛错
- 找到合适的函数
- 找到的候选集合 为空,或者 > 1,则失败,否则执行这个匹配
可以把上面的例子改为 C++ 11 实现
1
2
3
4
5
6
7
|
// C++11:
template <class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
foo(T t) {
std::cout << "foo<arithmetic T>\n";
return t;
}
|
这里会解析传入的函数参数类型,如果是 非算术类型,则拒绝
否则生成对应的类型T
C++ 14/17 中,对 std::is_arithmetic::value 做了简化
1
2
3
4
5
6
7
|
// C++17:
template <class T>
typename std::enable_if_t<std::is_arithmetic_v<T>, T> // << shorter!
foo(T t) {
std::cout << "foo<arithmetic T>\n";
return t;
}
|
完整的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <iostream>
#include <type_traits>
template <class T>
typename std::enable_if_t<std::is_arithmetic_v<T>, T> // << shorter!
foo(T t) {
std::cout << "foo<arithmetic T>\n";
return t;
}
template <class T>
typename std::enable_if_t<!std::is_arithmetic_v<T>, void>
foo(T t) {
std::cout << "foo fallback\n";
}
int main() {
foo(0);
foo(std::string{});
}
|
标签分发
一个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
template <typename T>
int get_int_value_impl(T t, std::true_type) {
return static_cast<int>(t+0.5f);
}
template <typename T>
int get_int_value_impl(T t, std::false_type) {
return static_cast<int>(t);
}
template <typename T>
int get_int_value(T t) {
return get_int_value_impl(t, std::is_floating_point<T>{});
}
|
根据std::is_floating_point<T>
判断是否满足条件
- 当返回 std::true_type,调用 static_cast(t+0.5f)
- 当返回 std::false_type,调用 static_cast(t)
在 C++ 17 中,可以使用if constexpr
,实现同样的编译期计算功能
1
2
3
4
5
6
7
8
9
|
template <typename T>
int get_int_value(T t) {
if constexpr (std::is_floating_point<T>) {
return static_cast<int>(t+0.5f);
}
else {
return static_cast<int>(t);
}
}
|
C++ 20 中,用concept
进一步简化代码,增加可读性
1
2
3
4
5
6
7
|
// define a concept:
template <class T>
concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>;
// use:
template <SignedIntegral T>
void signedIntsOnly(T val) { }
|
上面创建了 一个 concept为 SignedIntegral,它要满足条件:std::is_integral_v && std::is_signed_v
之后定义一个模版:template
concept
concept
是 C++20 的新功能,在编译期在模板参数上的一些约束,可以用于类模版和函数模版,控制期函数重载和部分特化
这里新增了两个关键字
一个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <concepts>
template <typename T>
concept Sortable = requires(T t) {
std::sort(t.begin(), t.end()); // Requires the type to be sortable
};
template <typename Container>
requires Sortable<Container>
auto findMax(const Container& container) {
return *std::max_element(container.begin(), container.end());
}
int main() {
std::vector<int> numbersVec = {5, 1, 9, 3, 7};
std::list<double> numbersList = {2.5, 1.1, 4.7, 3.3};
int maxVec = findMax(numbersVec);
double maxList = findMax(numbersList);
std::cout << "Max in vector: " << maxVec << std::endl;
std::cout << "Max in list: " << maxList << std::endl;
}
|
解释
- 定义了一个 concept:Sortable,确保类型 T支持 std::sort
- 函数findMax,对于任何满足 Sortable 约束的参数都可以工作
- findMax 增加了约束:requires Sortable
编译 && 执行
1
2
3
4
|
g++ -o concept -std=c++20 concept.cpp -lstdc++
Max in vector: 9
Max in list: 4.7
|
另一个例子,检查模版参数是否包含std::string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
template<typename T>
concept has_string_data_member = requires(T v) {
{ v.name_ } -> std::convertible_to<std::string>;
};
struct Person {
int age_ { 0 };
std::string name_;
};
struct Box {
double weight_ { 0.0 };
double volume_ { 0.0 };
};
int main() {
static_assert(has_string_data_member<Person>);
static_assert(!has_string_data_member<Box>);
}
|
约束条件可以有多个:
1
2
3
4
5
6
|
template <typename T>
concept Clock = requires(T c) {
c.start();
c.stop();
c.getTime();
};
|
从语言进化角度看
一个例子,假设对于整数,相等就直接比较 ==
如果是浮点数,如果两个数的 相减的 绝对者,小于一个阈值,则也认为是相等
C++11/14 中可以这么写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
template <class T> constexpr T absolute(T arg) {
return arg < 0 ? -arg : arg;
}
template <class T>
constexpr enable_if_t<is_floating_point<T>::value, bool>
close_enough(T a, T b) {
return absolute(a - b) < static_cast<T>(0.000001);
}
template <class T>
constexpr enable_if_t<!is_floating_point<T>::value, bool>
close_enough(T a, T b) {
return a == b;
}
|
C++17 通过if constexpr
可以让代码可读性更好
1
2
3
4
5
6
7
8
9
10
11
12
13
|
template <class T> constexpr T absolute(T arg) {
return arg < 0 ? -arg : arg;
}
template <class T>
constexpr auto precision_threshold = T(0.000001);
template <class T> constexpr bool close_enough(T a, T b) {
if constexpr (is_floating_point_v<T>) // << !!
return absolute(a - b) < precision_threshold<T>;
else
return a == b;
}
|
C++20 通过约束,让代码可读性进一步提升
1
2
3
4
5
|
template <typename T>
requires std::is_floating_point_v<T>
constexpr bool close_enough20(T a, T b) {
return absolute(a - b) < precision_threshold<T>;
}
|
最上面 C++11/14的那个里子,也可以用C++98/03 实现,但是写起来会更复杂
而且现在也很少有人用这么老的编译器了,CentOS默认的编译器都是 4.8.5,可以支持 C++11了
C++98的对于模版的语法,就可以忽略了吧。。。
concept中最核心的就是约束的表达式
- Conjunctions(与)
- Disjunctions(或)
- Atomic Constraints(原子约束)
1
2
3
4
5
|
template<class T> constexpr bool is_a = true;
template<class T> constexpr bool is_b = true;
template<class T>
concept concept_a_or_b = is_a<T> || is_b<T>;
|
concept_a_or_b含有2个原子约束,然后通过disjunctions组合而成
总结
总之,现代C++的目的之一,就是让代码的可读性越来越好
没有concept
也可以用其他方式实现类似的功能,但是可读性就不行了
Concepts 通过将模板的类型约束抽象出来,然后在模板定义时再使用
这样成功解耦了模板类型约束和模板本身的一些类型逻辑
参考