前言
今天我们来谈谈条件编译。条件编译是非常有意思和方便的东西,它类似于函数重载,对于不同的类型使用不同的算法。这是传统的一个函数重载的示例,我们姑且将它算作条件编译:
1 2 3 4 5 6 7 8 9 10
| int to_int(int i) { return i; } int to_int(double f) { return f; } int to_int(const char *s) { return atoi(s); } int to_int(const std::string &s) { return std::stoi(s); } int to_int(const std::vector<char> &v) { int ret = 0; for (char x : v) ret = ret << 8 | static_cast<unsigned int>(ret); return ret; }
|
但是,这种传统的条件编译存在两个问题:
- 不断有类型被定义,而函数重载可以接受的类型是有限的,我们必须要为其新增新的重载。
- 在模板元编程中,模板元无条件接受一个类型,但我可能需要对不同类型进行不同处理,我们可以使用模板元特化,但它将存在与函数重载相同的问题,且更加严重。(主要原因)
于是我们将介绍 std::enable_if
(C++ 11) / if constexpr
(C++ 17) / concept
(C++ 20) 用于处理这个问题。在后面的示例中,我们将用不同 C++ 版本来实现释放内存的操作,注意我们释放的内存可能是指针的,也可能是智能指针的。
std::enable_if(C++ 11)
从 C++ 11
开始支持编译时类型判断,这些函数被包含在 type_traits
标准库中。在 C++ 11
中引入的 enable_if
则允许我们启用或禁用模板,这结合 type_traits
非常方便。
1 2 3 4 5 6 7 8 9 10 11
|
template <typename T> typename std::enable_if<std::is_pointer<T>::value, void>::type destroy(T &p) { delete p; }
template <typename T> typename std::enable_if<is_smart_ptr<T>::value, void>::type destroy(T &p) { p.reset(); }
|
额,但是如果 is_smart_ptr
是被标准库支持的那么很方便,但是可能需要我们自己来支持一下 is_smart_ptr
,这里有两种解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
template <typename T> class is_smart_ptr : public std::false_type {};
template <typename T> class is_smart_ptr<std::shared_ptr<T>> : public std::true_type {};
template <typename T> class is_smart_ptr<std::unique_ptr<T>> : public std::true_type {};
template <typename T, typename D> class is_smart_ptr<std::unique_ptr<T, D>> : public std::true_type {};
template <typename T> class is_smart_ptr<std::weak_ptr<T>> : public std::true_type {};
|
1 2 3 4 5 6 7 8 9 10 11 12 13
|
template <typename T, typename = void> class is_smart_ptr : public std::false_type {};
template <typename T> class is_smart_ptr<T, decltype(typename T::element_type(), void())> : public std::true_type {};
|
变量模板(C++ 14)
在上一个示例中,我们注意到一个问题:enable_if
每次都要 ::type
,而 is_shared_ptr
每次都要 ::value
。其实从 C++ 14
开始支持了 xxx_t
/ xxx_v
系列模板变量,其实际源于 C++ 14
支持了变量模板,提供了一些便利。
1 2 3 4 5 6 7 8 9 10 11 12 13
| template <typename T> constexpr bool is_pointer_v = std::is_pointer<T>::value;
template <typename T> constexpr bool is_smart_ptr_v = is_smart_ptr<T>::value;
template <typename T> typename std::enable_if_t<is_pointer_v<T>, void> destroy(T &p) { delete p; }
template <typename T> typename std::enable_if_t<is_smart_ptr_v<T>, void> destroy(T &p) { p.reset(); }
|
if constexpr(C++ 17)
但是 C++ 11
提供的 enable_if
多少有点奇怪,我们必须写两个模板。而 C++ 17
开始支持的 if constexpr
允许你像使用条件控制语句一样使用函数,这更符合常规编程思维。当然它也不能完全替代 enable_if
,毕竟完全分离具有更高的灵活性。
1 2 3 4 5 6 7 8 9 10
| template <typename T> void destroy(T &p) { if constexpr (std::is_pointer_v<T>) { cout << "delete p" << endl; delete p; } else { cout << "p.reset" << endl; p.reset(); } }
|
if constexpr
还有一个优点,就是 enable_if
必须保证是没有二义性的,而 if_constexpr
可以使用 else
。
concept(C++ 20)
C++ 20
的 concept
是对于 C++ 11
的 enable_if
的进一步规范化。使用 concept
提高了条件模板的复用性和可读性。另外我们几乎不再需要显式触发 SFINAE
,这降低了语法难度,而且你还可以得到更友好的 IDE
支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| template <typename T> concept Pointer = std::is_pointer_v<T>;
template <typename T> concept SmartPointer = requires(T t) { typename T::element_type; t.reset(); };
template <Pointer T> void destroy(T &p) { delete p; }
template <SmartPointer T> void destroy(T &p) { p.reset(); }
|