C++17之std::invoke: 使用和原理探究(全)

目錄

1.概述

2.輔助類

3.原理分析

4.總結


1.概述

????????在之前的 C++ 版本中,要調用不同類型的可調用對象,需要使用不同的語法,例如使用函數調用運算符 () 來調用函數或函數指針,使用成員訪問運算符 -> 或 . 來調用成員函數。這樣的語法差異導致了代碼的冗余和不一致,給編寫和維護代碼帶來了困擾。

????????std::invoke?是 C++17標準庫中引入的一個函數模板,它的引入就是為了解決這個問題,它提供了一種統一的調用語法,無論是調用普通函數、函數指針、類成員函數指針、仿函數、std::function、類成員還是lambda表達式,都可以使用相同的方式進行調用。

????????std::invoke?的語法如下:

template <typename Fn, typename... Args>
decltype(auto) invoke(Fn&& fn, Args&&... args);

它接受一個可調用對象 fn 和相應的參數 args...,并返回調用結果。例如:

#include <functional>
#include <iostream>
#include <type_traits>struct Foo
{Foo(int num) : num_(num) {}void print_add(int i) const { std::cout << num_ + i << '\n'; }int num_;
};void print_num(int i)
{std::cout << i << '\n';
}struct PrintNum
{void operator()(int i) const{std::cout << i << '\n';}
};int main()
{// 調用自由函數std::invoke(print_num, -9);// 調用 lambdastd::invoke([]() { print_num(42); });// 調用成員函數const Foo foo(314159);std::invoke(&Foo::print_add, foo, 1);// 調用(訪問)數據成員std::cout << "num_:" << std::invoke(&Foo::num_, foo) << '\n';// 調用函數對象std::invoke(PrintNum(), 18);#if defined(__cpp_lib_invoke_r)auto add = [](int x, int y) { return x + y; };auto ret = std::invoke_r<float>(add, 11, 22);static_assert(std::is_same<decltype(ret), float>());std::cout << ret << '\n';std::invoke_r<void>(print_num, 44);
#endif
}

可能的輸出:

-9
42
314160
num_:314159
18
33
44

????????通過?std::invoke,我們可以在不關心可調用對象的具體類型的情況下進行調用,提高了代碼的靈活性和可讀性。它尤其適用于泛型編程中需要以統一方式調用各種可調用對象的場景,例如使用函數指針或成員函數指針作為模板參數的算法或容器等。

2.輔助類

? ? ? ? 閱讀后面的內容,你必須事先了解以下內容:

? ? ? ? 1.constexpr

? ? ? ? 2.std::is_base_of_v

? ? ? ? 3.std::remove_cv_t

? ? ? ? 4.std::ref和std::cref

? ? ? ? 5.std::is_member_function_pointer

? ? ? ? 6.std::is_member_object_pointer_v

? ? ? ? 7.左值和右值

3.原理分析

? ? ? ? 從上面的例子我們可以猜想,std::invoke的實現應該是根據傳入的參數Fn來判斷出Fn是否為可調用對象(Callable),常見的可調用對象有:

  • function?
  • member function
  • function object
  • lambda expression
  • bind expression
  • std::function

如果是可調用對象,那肯定也需要分析出是那種可調用對象,C++涉及到的可調用對象有:

? ? ? ? 1.普通函數,保證了對C的兼容。如:void? func(int x, int y);

? ? ? ? 2.函數指針。和數組名一樣,函數名即為函數指針。如:

	typedef void(*FType)(int); //定義一個函數指針類型Ftypevoid func(FType fn, int x) {fn(x);}

? ? ? ? 3.類成員變量和成員函數

	class CTestabcd{public:inline int func(int a, int b) { return a + b; }public:int  m_i;};using TestFunc = int (CTestabcd::*)(int, int);using TestMember = int(CTestabcd::*);TestFunc gTestFunc = &CTestabcd::func;TestMember gTestMember = &CTestabcd::m_i;

? ? ? ? 4.仿函數(函數對象),即重載了operator()運算符的類對象,如:

    template <class _Ty = void>struct less {_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool _RESULT_TYPE_NAME;_NODISCARD constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const {return _Left < _Right;}};

? ? ? ? std::bind綁定,它是STL的配接器,用于創建一個可調用的對象,對象里面重載了operator(),也是運用了仿函數的思想,如:?

#include <iostream>  
#include <functional>  
#include <thread>  
#include <chrono>  
#include <vector>  
#include <algorithm>  void print_sum(int x, int y) {  std::cout << x + y << "\n";  
}  int main() {  std::vector<int> nums = {1, 2, 3, 4, 5};  auto bound_sum = std::bind(print_sum, std::placeholders::_1, 5);  // 綁定第二個參數為 5。  std::for_each(nums.begin(), nums.end(), bound_sum);  // 對于每個元素,輸出它與 5 的和。  return 0;  
}

? ? ? ? 5.lambda表達式,如:

auto f = [] { return "hello world"; }; 
cout << f() << endl; // 輸出:hello world

? ? ? ? 6.std::function, 如:

#include <iostream>
#include <functional>// std::function
std::function<int(int, int)> SumFunction;// 普通函數
int func_sum(int a, int b)
{return a + b;
}class Calcu
{
public:int base = 20;// 類的成員方法,參數包含this指針int class_func_sum(const int a, const int b) const { return this->base + a + b; };// 類的靜態成員方法,不包含this指針static int class_static_func_sum(const int a, const int b) { return a + b; };
};// 仿函數
class ImitateAdd
{
public:int operator()(const int a, const int b) const { return a + b; };
};// lambda函數
auto lambda_func_sum = [](int a, int b) -> int { return a + b; };// 函數指針
int (*func_pointer)(int, int);int main(void) 
{int x = 2; int y = 5;// 普通函數SumFunction = func_sum;int sum = SumFunction(x, y);std::cout << "func_sum:" << sum << std::endl;// 類成員函數Calcu obj;SumFunction = std::bind(&Calcu::class_func_sum, obj, std::placeholders::_1, std::placeholders::_2); // 綁定this對象sum = SumFunction(x, y);std::cout << "Calcu::class_func_sum:" << sum << std::endl;// 類靜態函數SumFunction = Calcu::class_static_func_sum;sum = SumFunction(x, y);std::cout << "Calcu::class_static_func_sum:" << sum << std::endl;// lambda函數SumFunction = lambda_func_sum;sum = SumFunction(x, y);std::cout << "lambda_func_sum:" << sum << std::endl;// 帶捕獲的lambda函數int base = 10;auto lambda_func_with_capture_sum = [&base](int x, int y)->int { return x + y + base; };SumFunction = lambda_func_with_capture_sum;sum = SumFunction(x, y);std::cout << "lambda_func_with_capture_sum:" << sum << std::endl;// 仿函數ImitateAdd imitate;SumFunction = imitate;sum = SumFunction(x, y);std::cout << "imitate func:" << sum << std::endl;// 函數指針func_pointer = func_sum;SumFunction = func_pointer;sum = SumFunction(x, y);std::cout << "function pointer:" << sum << std::endl;getchar();return 0;
}

? ? ? ? 通過上面的講解,那我們看看std::invoke是不是這樣去判斷的呢?(以vs2019為藍本),先看看源碼:

//[1]函數沒有參數的調用方式
template <class _Callable>
_CONSTEXPR17 auto invoke(_Callable&& _Obj) noexcept(noexcept(static_cast<_Callable&&>(_Obj)()))-> decltype(static_cast<_Callable&&>(_Obj)()) {return static_cast<_Callable&&>(_Obj)();
}//[2]除1之外的其他調用方式
template <class _Callable, class _Ty1, class... _Types2>
_CONSTEXPR17 auto invoke(_Callable&& _Obj, _Ty1&& _Arg1, _Types2&&... _Args2) noexcept(noexcept(_Invoker1<_Callable, _Ty1>::_Call(static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)))-> decltype(_Invoker1<_Callable, _Ty1>::_Call(static_cast<_Callable&&>(_Obj), static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...)) {if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Functor) {return static_cast<_Callable&&>(_Obj)(static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_object) {return (static_cast<_Ty1&&>(_Arg1).*_Obj)(static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_refwrap) {return (_Arg1.get().*_Obj)(static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmf_pointer) {return ((*static_cast<_Ty1&&>(_Arg1)).*_Obj)(static_cast<_Types2&&>(_Args2)...);} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_object) {return static_cast<_Ty1&&>(_Arg1).*_Obj;} else if constexpr (_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_refwrap) {return _Arg1.get().*_Obj;} else {static_assert(_Invoker1<_Callable, _Ty1>::_Strategy == _Invoker_strategy::_Pmd_pointer, "bug in invoke");return (*static_cast<_Ty1&&>(_Arg1)).*_Obj;}
}

從上面的代碼可以看到,傳入參數 _Obj 的型別判斷是通過類 _Invoker1 萃取出來的,那現在來看一下_Invoker1的廬山真面目吧:

//【1】
template <class _Callable, class _Ty1, class _Removed_cvref = _Remove_cvref_t<_Callable>,bool _Is_pmf = is_member_function_pointer_v<_Removed_cvref>,bool _Is_pmd = is_member_object_pointer_v<_Removed_cvref>>
struct _Invoker1;//【2】
template <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, true, false>: conditional_t<is_base_of_v<typename _Is_memfunptr<_Removed_cvref>::_Class_type, remove_reference_t<_Ty1>>,_Invoker_pmf_object,conditional_t<_Is_specialization_v<_Remove_cvref_t<_Ty1>, reference_wrapper>, _Invoker_pmf_refwrap,_Invoker_pmf_pointer>> {}; // pointer to member function//【3】
template <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, false, true>: conditional_t<is_base_of_v<typename _Is_member_object_pointer<_Removed_cvref>::_Class_type, remove_reference_t<_Ty1>>,_Invoker_pmd_object,conditional_t<_Is_specialization_v<_Remove_cvref_t<_Ty1>, reference_wrapper>, _Invoker_pmd_refwrap,_Invoker_pmd_pointer>> {}; // pointer to member data//【4】
template <class _Callable, class _Ty1, class _Removed_cvref>
struct _Invoker1<_Callable, _Ty1, _Removed_cvref, false, false> : _Invoker_functor {};

1)在【1】處通過?is_member_function_pointer_v 判斷是類成員函數指針,通過?is_member_object_pointer_v 判斷是類成員變量

2)在【2】處指示的的是類成員函數指針,判斷參數_Arg1是否為reference_wrapper類型的,即是傳入對象添加了std::ref或std::cref包裝。

3)在【3】處指示的是類成員變量指針,判斷參數_Arg1是否為reference_wrapper類型的,即是傳入對象添加了std::ref或std::cref包裝。

4)在【4】處指示的是除【2】,【3】之外的函數。

型別推導出的類型有:

enum class _Invoker_strategy {_Functor,   //普通函數,仿函數,lamdba表達式, std::function等_Pmf_object, //類成員函數,傳遞的是對象_Pmf_refwrap, //類成員函數,傳遞的是用std::ref或std::cref包裝了的對象_Pmf_pointer, //類成員函數,傳遞的是對象的指針_Pmd_object,  //類成員變量,傳遞的是對象_Pmd_refwrap, //類成員變量,傳遞的是用std::ref或std::cref包裝了的對象_Pmd_pointer  //類成員變量,傳遞的是對象的指針
};

至此,std::invoke的實現原理很清晰了吧。

4.總結

? ? ? ? std::invoke用起來是十分的方便,方便的背后是系統幫你做了很多影藏的東西。也同樣看出,C++的模版是多么的強大。如果喜歡就快去使用吧!

????????喜歡的同學點贊收藏唄!

參考:std::invoke, std::invoke_r - cppreference.com

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/714348.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/714348.shtml
英文地址,請注明出處:http://en.pswp.cn/news/714348.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

二維碼門樓牌管理系統技術服務的深度解析

文章目錄 前言一、標準地址名稱的定義與重要性二、二維碼門樓牌管理系統的核心技術三、標準地址名稱在二維碼門樓牌管理中的應用四、二維碼門樓牌管理系統的優勢與挑戰五、展望未來 前言 在數字化浪潮中&#xff0c;二維碼門樓牌管理系統以其高效、便捷的特性&#xff0c;正逐…

【一】【算法分析與設計】基礎測試

排列式 題目描述 7254是一個不尋常的數&#xff0c;因為它可以表示為7254 39 x 186&#xff0c;這個式子中1~9每個數字正好出現一次 輸出所有這樣的不同的式子&#xff08;乘數交換被認為是相同的式子&#xff09; 結果小的先輸出&#xff1b;結果相同的&#xff0c;較小的乘…

js 實戰小案例

實戰 時間 js 格式化時間 <script type"text/javascript">function formatDate(date) { let year date.getFullYear(); let month String(date.getMonth() 1).padStart(2, 0); // getMonth() 返回的月份是從0開始的&#xff0c;所以要加1&#xff0c;并…

【go從入門到精通】go包,內置類型和初始化順序

大家好&#xff0c;這是我給大家準備的新的一期專欄&#xff0c;專門講golang&#xff0c;從入門到精通各種框架和中間件&#xff0c;工具類庫&#xff0c;希望對go有興趣的同學可以訂閱此專欄。 go基礎 。 Go文件名&#xff1a; 所有的go源碼都是以 ".go" 結尾&…

Mamba 環境安裝:causal-conv1d和mamba-ssm報錯解決辦法

問題描述&#xff1a; 在執行命令 pip install causal_conv1d 和 mamba_ssm 出錯&#xff1a; 解決方案&#xff1a; 1、使用網友配置好的Docker環境&#xff0c;參考&#xff1a;解決causal_conv1d和mamba_ssm無法安裝 -&#xff1e; 直接使用Mamba基礎環境docker鏡像 DockH…

java實現圖片轉pdf,并通過流的方式進行下載(前后端分離)

首先需要導入相關依賴&#xff0c;由于具體依賴本人也不是記得很清楚了&#xff0c;所以簡短的說一下。 iText&#xff1a;PDF 操作庫&#xff0c;用于創建和操作 PDF 文件。可通過 Maven 或 Gradle 引入 iText 依賴。 MultipartFile&#xff1a;Spring 框架中處理文件上傳的類…

一臺工控機的能量

使用Docker搭建EPICS的IOC記錄 Zstack EPICS Archiver在小課題組的使用經驗 以前電子槍調試&#xff0c;用一臺工控機跑起束測后臺&#xff0c;這次新光源用的電子槍加工回來又是測試&#xff0c;又是用一臺工控機做起重復的事&#xff0c;不過生命在于折騰&#xff0c;重復的…

stm32——hal庫學習筆記(IIC)

一、IIC總線協議介紹&#xff08;掌握&#xff09; 二、AT24C02介紹&#xff08;了解&#xff09; 三、AT24C02讀寫時序&#xff08;掌握&#xff09; 四、AT24C02驅動步驟&#xff08;掌握&#xff09; 五、編程實戰&#xff08;掌握&#xff09; myiic.c #include "./B…

汽車虛擬仿真技術的實現、應用和未來

汽車虛擬仿真技術是一種利用計算機模擬汽車運行的技術&#xff0c;以實現對汽車行為的分析、評估和改進。汽車虛擬仿真技術是汽車工業中重要的開發設計和測試工具&#xff0c;可以大大縮短產品研發周期、降低研發成本和提高產品質量。本文將從汽車虛擬仿真技術的實現過程、應用…

Ubuntu18.04 系統上配置并運行SuperGluePretrainedNetwork(僅使用CPU)

SuperGlue是Magic Leap在CVPR 2020上展示的研究項目&#xff0c;它是一個圖神經網絡&#xff08;Graph Neural Network&#xff09;和最優匹配層&#xff08;Optimal Matching layer&#xff09;的結合&#xff0c;訓練用于對兩組稀疏圖像特征進行匹配。這個項目提供了PyTorch代…

前端的文字的字體應該如何設置

要設置文字的字體&#xff0c;在CSS中使用font-family屬性。這個屬性可以接受一個或多個字體名稱作為其值&#xff0c;瀏覽器會按照列表中的順序嘗試使用這些字體渲染文本。如果第一個字體不可用&#xff0c;瀏覽器會嘗試使用列表中的下一個字體&#xff0c;依此類推。 字體設…

iOS消息發送流程

Objc的方法調用基于消息發送機制。即Objc中的方法調用&#xff0c;在底層實際都是通過調用objc_msgSend方法向對象消息發送消息來實現的。在iOS中&#xff0c; 實例對象的方法主要存儲在類的方法列表中&#xff0c;類方法則是主要存儲在原類中。 向對象發送消息&#xff0c;核心…

推薦一個屏幕上鼠標高亮顯示的小工具

在視頻錄制等特定場景下&#xff0c;很多人希望在點擊鼠標時能夠在屏幕上及時進行顯示&#xff0c;便于別人發現&#xff0c;提高別人的注意力。 因此&#xff0c;很多錄屏軟件中都內含顯示鼠標點擊功能。那如果不支持該怎么辦呢&#xff1f;其實&#xff0c;也是可以通過其他工…

Python 實現Excel自動化辦公(上)

在Python 中你要針對某個對象進行操作&#xff0c;是需要安裝與其對應的第三方庫的&#xff0c;這里對于Excel 也不例外&#xff0c;它也有對應的第三方庫&#xff0c;即xlrd 庫。 什么是xlrd庫 Python 操作Excel 主要用到xlrd和xlwt這兩個庫&#xff0c;即xlrd是讀Excel &am…

算法刷題day20:二分系列

目錄 引言概念一、借教室二、分巧克力三、管道四、技能升級五、冶煉金屬六、數的范圍七、最佳牛圍欄 引言 這幾天一直在做二分的題&#xff0c;都是上了難度的題目&#xff0c;本來以為自己的二分水平已經非常熟悉了&#xff0c;沒想到還是糊涂了一兩天才重新想清楚&#xff0…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的口罩識別系統(Python+PySide6界面+訓練代碼)

摘要&#xff1a;開發口罩識別系統對于提升公共衛生安全和疫情防控具有重要意義。本篇博客詳細介紹了如何利用深度學習構建一個口罩識別系統&#xff0c;并提供了完整的實現代碼。該系統基于強大的YOLOv8算法&#xff0c;并結合了YOLOv7、YOLOv6、YOLOv5的對比&#xff0c;給出…

Linux:syslog()的使用和示例

man手冊 命令行man openlog即可查看;寫的非常詳細&#xff0c;看完其實就懂了。 NAME closelog, openlog, syslog, vsyslog - send messages to the system logger SYNOPSIS #include <syslog.h> void openlog(const char *ident, int option, int facili…

刷題筆記day27-回溯算法2

216. 組合總和 III 這個思路還是&#xff0c;三部曲&#xff1a; 終止條件處理單層節點回溯節點 題中說的是&#xff0c;1到9的數&#xff0c;不能有重復。 k個數&#xff0c;和為n。 那么只要 len(path) k 的時候&#xff0c;判斷 n 為0&#xff0c;就可以入切片了。 fun…

如何更好的引導大語言模型進行編程的高效開發流程?

這張圖片展示了一種如何更好地引導大語言模型進行編程的方法。 首先&#xff0c;最簡單也是最有效的方法是讓大語言模型重復運行多次&#xff0c;每次增加一些額外的信息&#xff0c;直到獲得想要的結果。這種方法雖然簡單&#xff0c;但可能需要多次嘗試才能得到滿意的結果。…

2024綠色能源、城市規劃與環境國際會議(ICGESCE 2024)

2024綠色能源、城市規劃與環境國際會議(ICGESCE 2024) 一、【會議簡介】 隨著全球氣候變化和環境問題日益嚴重&#xff0c;綠色能源和可持續發展已成為全球關注的焦點。本次會議旨在匯聚全球在綠色能源、城市規劃與環境領域的專家、學者和實踐者&#xff0c;共同探討和分享關于…