C++17使用折疊表達式實現(xiàn)一個IsAllTrue函數(shù)的過程
前言
讓我們實現(xiàn)一個 IsAllTrue
函數(shù),支持變長參數(shù),可傳入多個表達式,必須全部計算為true,該函數(shù)才返回true。
本文記錄了逐步實現(xiàn)與優(yōu)化該函數(shù)的思維鏈,用到了以下現(xiàn)代C++新特性知識,適合對C++進階知識有一定了解的人。這樣一種從實際問題來學(xué)習(xí)和運用知識的過程還是挺有趣的,特此整理分享一下。
- 可變長參數(shù)模板 (C++11)
- 折疊表達式 (C++17)
- 條件編譯 if constexpr (C++17)
- 類型萃取 type traits (C++11)
- 完美轉(zhuǎn)發(fā)
std::forward
(C++11) - 結(jié)構(gòu)化綁定
std::bind
(C++11)
初級版本——基于初始化列表實現(xiàn)
可以使用初始化列表 std::initializer_list
存儲多個bool變量,實現(xiàn)傳入多個bool值的目的,這種方法實際上該函數(shù)只有一個參數(shù),實現(xiàn)如下:
bool IsAllTrue(const std::initializer_list<bool>& conditions) { return std::all_of(conditions.begin(), conditions.end(), [](const bool a) { return a; }); }
使用方法如下:
int a = 1; bool b = true; auto c = []() {return true;} IsAllTrue({a, b, c});
這個方法的實現(xiàn)簡單易用,但是對于代碼有更高追求的人并不滿足于此,以上實現(xiàn)存在如下問題:
- 傳入?yún)?shù)是一個初始化列表,需要寫大括號{},不夠優(yōu)雅。
- 調(diào)用函數(shù)前計算了每一個條件表達式,但實際任意一個為false,即可返回,可能存在如下問題:
不必要的函數(shù)調(diào)用帶來一定計算開銷;
當(dāng)前后表達式存在依賴關(guān)系時,比如
p && p →a
,如果p是指針且為空, 計算p→a
會導(dǎo)致程序崩潰。
對于不了解這個函數(shù)用法的人而言,使用這個實現(xiàn)是會存在一定風(fēng)險的。所以我們需要想辦法利用 &&
實現(xiàn)短路求值,以及對函數(shù)結(jié)果的延遲計算。
進階版本——基于折疊表達式實現(xiàn)
折疊表達式(Fold expressions)
折疊表達式是C++17引入的新特性,可通過二元操作符折疊可變長參數(shù)模板中的參數(shù)包。這個特性的引入是為了簡化C++11可變長參數(shù)模板的使用。
- 根據(jù)左右方向可分為左折疊和右折疊:
一元左折疊(Unary right fold)和一元右折疊(Unary left fold)形式如下:
( pack op... ) //一元右折疊,從右往左計算, 等同于(E1 op (... op (EN-1 op EN))) ( ... op pack ) //一元左折疊,從左往右計算, 等同于(((E1 op E2) op ...) op EN)
在大多數(shù)情況下,對于交換律成立的操作符(如 +
和 *
),左折疊和右折疊的結(jié)果是相同的。然而,對于非交換的操作符,結(jié)果可能不同,例如減法或除法。
- 根據(jù)是否有初始值可分為一元和二元:
二元折疊表達式分為:二元右折疊(Binary right fold)和 二元左折疊(Binary left fold)。
( pack op ... op init ) //二元右折疊 ( init op ... op pack ) //二元左折疊
- 使用二元左折疊的例子
template<typename... Args> void printer(Args&&... args) { ((std::cout<< args << " "), ...)<< "\n"; }
基于一元右折疊的IsAllTrue函數(shù)
基于 &&
運算符的一元右折疊(Unary right fold)實現(xiàn)IsAllTrue如下:
template<typename... Args> bool IsAllTrue(Args... args) { return (std::forward<Args>(args) && ...); }
- 注:折疊表達式的最外層括號是必須的。
但以上實現(xiàn),該模板本質(zhì)上仍只能支持變長的多個bool參數(shù),這會導(dǎo)致先計算出bool值再傳入,仍未實現(xiàn)函數(shù)結(jié)果的延遲計算。
使用type traits 進一步優(yōu)化
如何可以實現(xiàn)延遲計算呢?首先我們可以明確下,傳遞給該函數(shù)的參數(shù)類型,可能是bool值、可以計算出bool值的表達式或可調(diào)用對象、可轉(zhuǎn)換為bool值的指針和數(shù)值。
總體可分為兩類,一類是可轉(zhuǎn)換為bool的表達式,另一類是可計算出bool的可調(diào)用對象。
由于參數(shù)類型(bool、函數(shù)對象、指針等)和類型特征(是否可調(diào)用、是否可以轉(zhuǎn)成bool)均是可以在編譯期確定的。
為了避免在編譯期把模板參數(shù)類型都推斷為bool,可定義 IsTrue
函數(shù)模板定義表達式bool值的計算方式,使模板可以推斷出原表達式自身的類型,從而可以延遲其計算過程。其中用到了編譯期條件if constexpr
和 一種類型萃取是否可調(diào)用 std::is_invocable_v
,這兩個均是C++17引入的特性。
如果具備可調(diào)用的特征,則進行函數(shù)調(diào)用并返回結(jié)果;否則,將其轉(zhuǎn)換為bool值返回。實現(xiàn)如下:
template <typename T> bool IsTrue(T&& value) { if constexpr (std::is_invocable_v<T>) { // 如果是可調(diào)用對象,調(diào)用它并返回結(jié)果 return std::forward<T>(value)(); } else { // 否則,將其轉(zhuǎn)換為bool return static_cast<bool>(std::forward<T>(value)); } }
基于以上模板改寫 IsAllTure
模板函數(shù) :
template <typename... Args> bool IsAllTrue(Args&&... args) { return (IsTrue(std::forward<Args>(args)) && ...); }
該實現(xiàn)的本質(zhì)是我們希望在用N個表達式傳入該模板函數(shù)后,模板實例化為形同如下形式,從而可以實現(xiàn)短路機制:
static_cast<bool>(Expr1) && Expr2() && static_cast<bool>(Expr3) && ... && ExprN()
函數(shù)測試
對以上代碼進行如下測試,注釋為輸出結(jié)果,可以看到,能夠滿足我們的需求:
auto lambdaTrue = []() { std::cout<<" lambda true"<<std::endl; return true; }; auto lambdaFalse = []() { std::cout<<" lambda false"<<std::endl; return false; }; class Foo { public: int a; }; Foo* p = nullptr; IsAllTrue(true, lambdaTrue); // 輸出lambda true IsAllTrue(false, lambdaTrue); // 無輸出,實現(xiàn)了短路機制以及延遲計算 IsAllTrue(p, p->a); // 正常運行,不會coredump
以上為了方便,均使用定義了無參lambda函數(shù)進行了測試。為了延遲一般含參函數(shù)的計算結(jié)果,能夠方便傳入帶參數(shù)的函數(shù)對象,還可以基于std::bind
實現(xiàn)一個用于生成可調(diào)用對象的函數(shù):
template <typename F, typename... Args> auto make_callable(F&& f, Args&&... args) { return std::bind(std::forward<F>(f), std::forward<Args>(args)...); }
CPP 復(fù)制 全屏
比如:
bool less(int a, int b) { return a < b; } IsAllTrue(true, make_callable(less, 1, 2));
完整測試代碼:https://compiler-explorer.com/z/fTvq7Y36Y
知識總結(jié)
本文使用了以下C++知識實現(xiàn)了一個高效的IsAllTrue函數(shù),優(yōu)點為它的使用安全且較為高效,缺點在于代碼實現(xiàn)較為復(fù)雜,對C++知識掌握程度要求較高,過多使用也會導(dǎo)致代碼體積膨脹。
- 條件編譯
if constexpr
:- 這個關(guān)鍵字用于在編譯時判斷是否滿足條件。如果
T
是可調(diào)用對象(例如lambda
或函數(shù)對象),則調(diào)用它并返回結(jié)果。 - 如果
T
不是可調(diào)用對象,則將其轉(zhuǎn)換為bool
。
- 這個關(guān)鍵字用于在編譯時判斷是否滿足條件。如果
- 類型萃取
std::is_invocable_v
:- 這是一個用于判斷類型
T
是否可調(diào)用的特性。如果T
是可調(diào)用對象,則std::is_invocable_v<T>
返回true
。 - 需要包含 <type_traits> 頭文件
- 這是一個用于判斷類型
- 完美轉(zhuǎn)發(fā)
std::forward
:std::forward<T>(value)
確保參數(shù)的完美轉(zhuǎn)發(fā),保留其左值或右值性質(zhì)。
- 可變長參數(shù)模板:支持可變數(shù)量的參數(shù)包,語法用
T ... args
表示。 - 折疊表達式
- 使用了C++17中的折疊表達式 ,它會對參數(shù)從左到右進行求值。
- 簡化了可變長參數(shù)模板的使用,提供了一種簡潔而直觀的方式來對參數(shù)包進行展開和操作,從而避免了遞歸或顯式循環(huán)的繁瑣。
- 結(jié)構(gòu)化綁定
std::bind
:可綁定參數(shù)args到一個函數(shù)f,并返回一個可調(diào)用對象。
參考
到此這篇關(guān)于C++17 使用折疊表達式實現(xiàn)一個IsAllTrue函數(shù)的文章就介紹到這了,更多相關(guān)C++ IsAllTrue函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++11標(biāo)準(zhǔn)庫 互斥鎖 <mutex> 詳解
這篇文章主要介紹了C++11標(biāo)準(zhǔn)庫互斥鎖 <mutex> 的相關(guān)知識,使用call_once()的時候,需要一個once_flag作為call_once()的傳入?yún)?shù),本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-07-07C語言實現(xiàn)數(shù)據(jù)結(jié)構(gòu)和雙向鏈表操作
這篇文章主要介紹了C語言實現(xiàn)數(shù)據(jù)結(jié)構(gòu)雙向鏈表操作,需要的朋友可以參考下2017-03-03linux C 打印錯誤信息和標(biāo)準(zhǔn)輸入輸出詳細(xì)介紹
這篇文章主要介紹了linux C 打印錯誤信息和標(biāo)準(zhǔn)輸入輸出詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-12-12