一、std::not_fn定義和說明
std::not_fn這個模板函數非常有意思,在前面我們學習過wrapper(包裝器),其實它就是通過封裝一個包裝器來實現返回值的非。它的基本定義如下:
template< class F >
/* 未指定 */ not_fn( F&& f );(1) (C++17 起)(C++20 起為 constexpr)
template< auto ConstFn >
constexpr /* 未指定 */ not_fn() noexcept;(2) (C++26 起)
這里面的f參數是一個Callable對象。它的受限條件為:
1、std::decay_t 須為可調用 (Callable) 并支持移動構造 (MoveConstructible)
2、std::is_constructible_v<std::decay_t, F> 的結果必須為 true
那么,問題就來了,為什么要搞這么簡單的一個東西呢?直接操作不更簡單么?
二、應用
老生再次常談一下,一切的技術的應用,跟場景的結合是無法獨立出來的。也就是說,std::not_fn的應用,也不是放置四海皆優秀的。在實際的開發中,可能會遇到很多種情況,比如普通的函數,類成員函數,仿函數,甚至新標準中的Lambda表達式等等。
在處理這些情況的時候兒,可能直接操作返回一個非的結果很簡單,也可能比較不簡單。更有可能雖然簡單但不好理解。而有的情況下開發者需要的不是一個簡單的結果而是一個非的函數,凡此種種,都可能會有不同的需要。
一般來說這種在上層進行封裝的應用,大多數情況下在底層應用比較多,比如本身就是庫或框架。一如前面看到的STL中的元函數(如std::is_integral等),在業務層展現應用的機會很少,但一旦到底層的庫編程,則應用大行其道。
三、混合應用
開發者經常會遇到這種問題,實現一個問題的一面(正面或反面)比較簡單,而實現另外一面則相對復雜一些。這種簡單和復雜不單指的實現上的簡單,也包括代碼閱讀上的簡單。比如實現一個返回True,需要一行代碼,而返回False則需要幾行代碼。這種情況下,直接修改函數本身是沒有問題的,但應用起來就等于是多實現了一次,而且如果需要將函數本身做為參數傳遞的話,這又是一個問題,很有可能在嵌套傳參時導致行為的變形。
這時,使用std::not_fn直接生成一個新的非的函數,應用起來就會非常清晰明了。下面看一個簡單的例程:
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>// 是否為偶數
bool is_even(int x) {return x % 2 == 0;
}int main() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};auto is_odd = std::not_fn(is_even);// 打印std::vector<int> odd_numbers;std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(odd_numbers), is_odd);std::cout << "Odd numbers: ";for (int num : odd_numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
其實在基礎庫的很類似的應用 中都可以應用到std::not_fn,在代碼層級更容易理解。
四、特點
std::not_fn主要是為了替代前面的替代std::not1,std::not2這類非相關的處理。畢竟后兩個依賴于std::unary_function 或 std::binary_function的實現。從設計角度看,這種應用方式首先就限定的靈活性及通用性或者理解為適配性不好。那么std::not_fn有什么特點呢:
1、優勢
a)更高一層的抽象可以讓開發者離開代碼內部的實現,專注于邏輯的實現
b)適應性強,對所有的類函數(如普通函數、類內部成員函數、仿函數等等)都支持
c)c++20后支持constexpr,可在編譯期進行優化
d)代碼更直觀,容易理解和維護
2、劣勢
a)過深的嵌套可能邏輯的復雜化和提高后期維護的復雜度
b)應用范圍受限,一般要求返回值的類型必須為可轉化為bool類型的數據類型
c)在很簡單的非應用時,std::not_fn并沒有優勢
所以開發者的頭腦中始終要有根弦警惕著,技術要善用而非亂用!
五、例程
下面看一個cppreference的例程:
#include <cassert>
#include <functional>bool is_same(int a, int b) noexcept
{return a == b;
}struct S
{int val;bool is_same(int arg) const noexcept { return val == arg; }
};int main()
{// 用于自由函數:auto is_differ = std::not_fn(is_same);assert(is_differ(8, 8) == false); // 等價于:!is_same(8, 8) == falseassert(is_differ(6, 9) == true); // 等價于:!is_same(8, 0) == true// 用于成員函數:auto member_differ = std::not_fn(&S::is_same);assert(member_differ(S{3}, 3) == false); // 等價于:S tmp{6}; !tmp.is_same(6) == false// 保持 noexcept 說明:static_assert(noexcept(is_differ) == noexcept(is_same));static_assert(noexcept(member_differ) == noexcept(&S::is_same));// 用于函數對象:auto same = [](int a, int b) { return a == b; };auto differ = std::not_fn(same);assert(differ(1, 2) == true); // 等價于:!same(1, 2) == trueassert(differ(2, 2) == false); // 等價于:!same(2, 2) == false#if __cpp_lib_not_fn >= 202306Lauto is_differ_cpp26 = std::not_fn<is_same>();assert(is_differ_cpp26(8, 8) == false);assert(is_differ_cpp26(6, 9) == true);auto member_differ_cpp26 = std::not_fn<&S::is_same>();assert(member_differ_cpp26(S{3}, 3) == false);auto differ_cpp26 = std::not_fn<same>();static_assert(differ_cpp26(1, 2) == true);static_assert(differ_cpp26(2, 2) == false);
#endif
}
例程非常簡單,其實可以理解為對函數指針F的一種非的反向控制。它可以提供更靈活的控制方式,而不必直接修改相關的代碼。
六、總結
std::not_fn本身并沒有什么難度。但只要認真想一下,其實就難明白,它其實就是讓編程變得更靈活和更容易控制。盡最大可能的減少對程序的整體的影響或產生副作用。特別是對已經存在的老的代碼的完善和更新的情況下,這種處理方式是一種非常合理和便捷的存在。
當把一個操作看成一個黑盒時,它們內外的交互就不存在了,那么無論對哪一方來說,這都是好事兒。簡單永遠是開發者追求的目標!