普通函數的參數中的auto

2.1 普通函數的參數中的auto

??? 從c++14起,lambda可以使用auto占位符聲明或者定義參數:???

 auto printColl = [] (const auto& coll) // generic lambda{ for (const auto& elem : coll) {std::cout << elem << '\n';}}

只要支持Lambda 內部的操作,占位符允許傳遞任何類型的參數:

std::vector coll{1, 2, 4, 5};...printColl(coll); // compiles the lambda for vector<int>printColl(std::string{"hello"}); // compiles the lambda for std::string

從C++20起,我們可以使用auto占位符給所有函數(包括成員函數和運算符):

void printColl(const auto& coll) // generic function
{for (const auto& elem : coll) {std::cout << elem << '\n';}
}

這樣的聲明是僅僅像聲明函數或者定義了如下一個模板:

template<typename T>
void printColl(const T& coll) // equivalent generic function
{for (const auto& elem : coll) {std::cout << elem << '\n';}
}

由于唯一的區別是不使用模板參數T。因此,這個特性也稱為縮寫函數模板語法

因為帶有auto的函數是函數模板,所以使用函數模板的所有規則都適用。如果在不同的編譯單元中分別調用,

那么auto參數函數的實現不能在cpp文件中,應該放到hpp文件中定義,以便在多個CPP文件中使用,并且不需要聲明為inline函數,因為模板函數總是inline

此外,還可以顯式指定模板參數:

void print(auto val)
{std::cout << val << '\n';
}print(64); // val has type intprint<char>(64); // val has type char

2.1.1 成員函數的auto參數

使用這個特性可以定義成員函數:

class MyType {

...

void assign(const auto& newVal);

};

等價于:

class MyType {

...

template<typename T>

void assign(const T& newVal);

};

然而,需要注意的是,模板不能在函數內部聲明。因此,通過這個特性,你不能在函數內部局部定義類或數據結構。

void foo()

{

struct Data {

void mem(auto); // ERROR can’t declare templates insides functions

};

}

2.2? auto的使用

使用auto帶來的好處和方便:auto的延遲類型檢查。

2.2.1 使用auto進行延遲類型檢查

??? 對于使用auto參數,實現具有循環依賴的代碼會更加容易。

??? 例如,考慮兩個使用其他類對象的類。要使用另一個類的對象,您需要其類型的定義;僅進行前向聲明是不夠的(除非只聲明引用或指針)。

class C2; // forward declarationclass C1 {
public:void foo(const C2& c2) const // OK{c2.print(); // ERROR: C2 is incomplete type}void print() const;
};class C2 {
public:void foo(const C1& c1) const{c1.print(); // OK}void print() const;
};

盡管您可以在類定義中實現C2::foo(),但您無法實現C1::foo(),因為為了檢查c2.print()的調用是否有效,編譯器需要C2類的定義。在上述代碼中,當C1的foo()函數調用c2.print()時,由于C2類的定義仍然是不完整的,編譯器無法確定該調用的有效性。因此,這將導致編譯錯誤。

因此,你必須在聲明兩個類的結構之后實現C2::foo():

#include <iostream>class C2;? // forward declarationclass C1
{
public:void foo(const C2& c2) const;void print() const { std::cout << "C1::print" << std::endl;};
};class C2
{
public:void foo(const auto& c1) const{c1.print(); // OK}void print() const { std::cout << "C2::print" << std::endl;};
};inline void C1::foo(const C2& c2) const // implementation (inline if in header)
{c2.print(); // OK
}int main(void)
{C1 c1;C2 c2;c1.foo(c2);c2.foo(c1);return 0;}

由于泛型函數在調用時會檢查泛型參數的成員,因此通過使用auto,您可以簡單地實現以下內容:

#include <iostream>class C1
{
public://template<typename C> void foo(const C& c2) constvoid foo(const auto& c2) const{c2.print(); // OK}void print() const { std::cout << "C1::print" << std::endl;};};class C2
{
public:void foo(const C1& c1) const{c1.print(); // OK}void print() const { std::cout << "C2::print" << std::endl;};
};int main(void)
{C1 c1;C2 c2;c1.foo(c2);c2.foo(c1);??return 0;
}

這并不是什么新鮮事物。當C1::foo()聲明為成員函數模板時,您將獲得相同的效果。然而,使用auto可以更容易地實現這一點。

請注意,使用auto允許調用者傳遞任意類型的參數,只要該類型提供一個名為print()的成員函數。如果您不希望如此,可以使用標準概念std::same_as來限制僅針對C2類型的參數使用該成員函數:

#include <concepts>class C2;
class C1 
{
public:void foo(const std::same_as<C2> auto& c2) const{c2.print(); // OK}void print() const;
};

...

對于概念而言,不完整類型也可以正常工作。這樣,使用std::same_as概念可以確保只有參數類型為C2時才能使用該成員函數。

2.2.2 auto參數函數與lambda的對比

auto參數函數不同于lambda。例如,不能傳遞一個沒有指定具體類型給泛型參數auto的函數:

bool lessByNameFunc(const auto& c1, const auto& c2) { // sorting criterion

??? return c1.getName() < c2.getName(); // compare by name

}

...

std::sort(persons.begin(), persons.end(), lessByNameFunc); // ERROR: can’t deduce type of parameters in sorting criterion

lessByNameFunc函數等價于:

template<typename T1, typename T2>

bool lessByName(const T1& c1, const T1& c2) { // sorting criterion

??? return c1.getName() < c2.getName(); // compare by name

}

由于未直接調用函數模板,編譯器無法在編譯階段將模板參數推導出。因此,必須顯式指定模板參數:

std::sort(persons.begin(), persons.end(),

lessByName<Customer, Customer>); // OK

使用lambda的時候,在傳遞lambda時不必指定模板參數的參數類型:

lessByNameLambda = [] (const auto& c1, const auto& c2) { // sorting criterion

??? return c1.getName() < c2.getName(); // compare by name

};

...

std::sort(persons.begin(), persons.end(), lessByNameLambda); // OK

原因在于lambda是一個沒有通用類型的對象。只有將該對象用作函數時才是通用的。

另一方面,顯式指定(簡寫)函數模板參數會更容易一些。

  • 只需在函數名后面傳遞指定的類型即可

????????? void printFunc(const auto& arg) {

?????????????? ...

????????? }

????????? printFunc<std::string>("hello"); // call function template compiled for std::string

對于泛型lambda,由于泛型lambda是一個具有泛型函數調用運算符operator()的函數對象。我們必須按照如下去做:要顯式指定模板參數,你需要將其作為參數傳遞給 operator():

auto printFunc = [] (const auto& arg) {

...

};

printFunc.operator()<std::string>("hello"); // call lambda compiled for std::string

對于通用lambda,函數調用運算符operator()是通用的。因此,您需要將所需的類型作為參數傳遞給operator(),以顯式指定模板參數。

2.3 auto參數其他細節

2.3.1 auto參數的基本約束

使用auto參數去聲明函數遵循的規則與它聲明lambda參數的規則相同:

  • 對于用auto聲明的每個參數,函數都有一個隱式模板參數。
  • auto參數可以作為參數包void foo(auto… args);相當于

Template<typename … Types>void foo(Types… args);

  • decltype(auto)是不允許使用的

?????????

縮寫函數模板仍然可以使用(部分)顯式指定的模板參數進行調用。模板參數的順序與調用參數的順序相同。

例如:

For example:void foo(auto x, auto y)
{...
}foo("hello", 42); // x has type const char*, y has type intfoo<std::string>("hello", 42); // x has type std::string, y has type intfoo<std::string, long>("hello", 42); // x has type std::string, y has type long

2.3.2 結合templateauto參數

簡化的函數模板仍然可以顯式指定模板參數,為占位符類型生成的模板參數可添加到指定參數之后:

template<typename T>

void foo(auto x, T y, auto z)

{

...

}

foo("hello", 42, '?'); // x has type const char*, T and y are int, z is char

foo<long>("hello", 42, '?'); // x has type const char*, T and y are long, z is char

因此,以下聲明是等效的(除了在使用auto的地方沒有類型名稱):

template<typename T>

void foo(auto x, T y, auto z);

等價于

template<typename T, typename T2, typename T3>

void foo(T2 x, T y, T3 z);

正如我們稍后介紹的那樣,通過使用概念作為類型約束,您可以約束占位參數以及模板參數。然后,模板參數可以用于此類限定。

例如,以下聲明確保第二個參數y具有整數類型,并且第三個參數z具有可以轉換為y類型的類型:

template<std::integral T>

void foo(auto x, T y, std::convertible_to<T> auto z)

{

...

}

foo(64, 65, 'c'); // OK, x is int, T and y are int, z is char

foo(64, 65, "c"); // ERROR: "c" cannot be converted to type int (type of 65)

foo<long,short>(64, 65, 'c'); // NOTE: x is short, T and y are long, z is char

請注意,最后一條語句以錯誤的順序指定了參數的類型。

模板參數的順序與預期不符可能會導致難以發現的錯誤。考慮以下示例:

#include <vector>
#include <ranges>void addValInto(const auto& val, auto& coll)
{coll.insert(val);
}template<typename Coll> // Note: different order of template parametersrequires std::ranges::random_access_range<Coll>
void addValInto(const auto& val, Coll& coll)
{coll.push_back(val);
}int main()
{std::vector<int> coll;addValInto(42, coll); // ERROR: ambiguous
}

由于在addValInto的第二個聲明中只對第一個參數使用了auto,導致模板參數的順序不同。根據被C++20接受的http://wg21.link/p2113r0,這意味著重載決議不會? 優先選擇第二個聲明勝過優先選擇第一個聲明,從而導致出現了二義性錯誤。

因此,在混合使用模板參數和auto參數時,請務必小心。理想情況下,使聲明保持一致。

2.3.3 函數參數使用auto的優缺點:

好處:

簡化代碼:使用auto作為參數類型可以減少代碼中的冗余和重復,特別是對于復雜的類型聲明。它可以使代碼更加簡潔、易讀和易于維護。

提高靈活性:auto參數可以適應不同類型的實參,從而提高代碼的靈活性。這對于處理泛型代碼或接受多種類型參數的函數非常有用。

減少錯誤:使用auto作為參數類型可以減少類型推導錯誤的機會。編譯器將根據實參的類型來確定參數的類型,從而降低了手動指定類型時可能出現的錯誤。

后果:

可讀性下降:使用auto作為參數類型會使函數的接口和使用方式不夠明確。閱讀代碼時,無法直接了解參數的預期類型,需要查看函數的實現或上下文來確定。

難以理解:對于復雜的函數或涉及多個參數的函數,使用auto作為參數類型可能會增加代碼的復雜性和難以理解的程度。閱讀和理解函數的功能和使用方式可能需要更多的上下文信息。

潛在的性能影響:使用auto作為參數類型可能會導致一些性能損失。編譯器需要進行類型推導和轉換,可能會引入額外的開銷。在性能敏感的場景中,這可能需要謹慎考慮。

總體而言,使用auto作為參數類型可以簡化代碼并提高靈活性,但也可能降低可讀性和理解性。在決定是否使用auto作為參數類型時,需要權衡其中的利弊,并根據具體情況做出適當的選擇。

#include <vector>

#include <vector>

#include <ranges>

void addValInto(auto& coll, const auto& val)

{

coll.insert(val);

}

template<typename Coll>

requires std::ranges::random_access_range<Coll>

void addValInto(Coll& coll, const auto& val)

{

coll.push_back(val);

}

int main()

{

std::vector<int> coll;

addValInto(coll, 42); // OK, 選擇第二個聲明

}

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

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

相關文章

【OS】AUTOSAR Os是如何啟動第一個Task的

目錄 前言 正文 1.總體概覽及背景介紹 1.1. Os默認的Hook配置 1.2 用戶Task的配置

Golang創建文件夾

方法 package zdpgo_fileimport ("os" )// AddDir 創建文件夾 func AddDir(dir string) error {if !IsExist(dir) {return os.MkdirAll(dir, os.ModePerm)}return nil }測試 package zdpgo_fileimport "testing"func TestAddDir(t *testing.T) {data : […

第八屆“英拿科技杯”上海高校金馬程序設計聯賽暨東華大學邀請賽

第八屆“英拿科技杯”上海高校金馬程序設計聯賽暨東華大學邀請賽 儀表盤所有提交榜單 I. 孤星 單點時限: 2.0 sec 內存限制: 512 MB &#x1d45b;(1≤103) 個干員&#xff0c;每個干員工資為 &#x1d464;&#x1d456;(1≤&#x1d464;&#x1d456;≤105)&#xff0c;貢獻…

JAVA云HIS醫院系統源碼 HIS源碼:云HIS系統與SaaS的關系

云HIS系統與SaaS的關系 云HIS系統是一種基于云計算技術的醫院信息系統&#xff0c;它采用B/S架構&#xff0c;通過云端SaaS服務的方式提供。用戶可以通過瀏覽器訪問云HIS系統&#xff0c;無需關注系統的部署、維護、升級等問題。云HIS系統通常具有模板化、配置化、智能化等特點…

react記錄部署

導語 React中的核心概念 1 虛擬DOM&#xff08;Virtual DOM&#xff09; 2 Diff算法&#xff08;虛擬DOM的加速器&#xff0c;提升React性能的法寶&#xff09; React主要的原理 Virtual DOM 虛擬DOM; 提供了一種不同的而又強大的方式來更新DOM&#xff0c; 代替直接的DOM操…

cuda11.8安裝torch2.0.1

pip install torch2.0.1 torchvision0.15.2 torchaudio2.0.2 --index-url https://download.pytorch.org/whl/cu118

hot100 -- 回溯(上)

目錄 &#x1f35e;科普 &#x1f33c;全排列 AC DFS &#x1f6a9;子集 AC DFS &#x1f382;電話號碼的字母組合 AC DFS &#x1f33c;組合總和 AC DFS &#x1f35e;科普 忘記 dfs 的&#xff0c;先看看這個&#x1f447; DFS&#xff08;深度優先搜索&#xf…

百度軟件測試面試經歷,期望薪資27K

一面 1、 請為百度搜索框設計測試用例&#xff1f; 2、百度設計框上線前需要進行那些測試&#xff1f; 界面測試&#xff0c;功能測試&#xff0c;性能測試&#xff0c;安全性測試&#xff0c;易用性測試&#xff0c;兼容性測試&#xff0c;UI測試。 3、如何查看http狀態碼…

重學java 38.創建線程的方式?

It is during our darkest moments that we must focus to see the light —— 24.5.24 一、第一種方式_繼承extends Thread方法 1.定義一個類,繼承Thread 2.重寫run方法,在run方法中設置線程任務(所謂的線程任務指的是此線程要干的具體的事兒,具體執行的代碼) 3.創建自定義線程…

基于灰狼優化算法優化支持向量機(GWO-SVM)回歸預測

代碼原理 基于灰狼優化算法優化支持向量機&#xff08;GWO-SVM&#xff09;的回歸預測代碼的原理和流程如下&#xff1a; 1. **初始化灰狼群體**&#xff1a;隨機生成一定數量的灰狼&#xff0c;并初始化它們的位置和速度。 2. **初始化SVM模型參數**&#xff1a;根據問題要…

【JAVA基礎之網絡編程】UDP和TCP協議以及三次握手和四次揮手的過程

&#x1f525;作者主頁&#xff1a;小林同學的學習筆錄 &#x1f525;mysql專欄&#xff1a;小林同學的專欄 目錄 1. 網絡編程 1.1 概述 1.2 網絡編程的三要素 1.2.1 IP地址 1.2.2 InetAddress 1.2.3 端口和協議 1.3 UDP協議 1.3.1 UDP發送數據 1.3.2 UDP接收數據 1.4…

C語言——小知識和小細節18

一、力扣題目 1、題目本體 2、題解 本題目我們使用異或分組的方法來解決。可以在我之前的文章《C語言——操作符CSDN博客》中看一下異或的特點。 由于異或的運算規則為相同為0&#xff0c;不同為1&#xff0c;而且是在二進制補碼上進行操作的&#xff0c;我們可以發現的一個…

c++|多態

c|多態 1 多態的概念2 多態的定義及其實現2.1 滿足多態的條件2.2 虛函數2.3 虛函數的重寫2.4 析構函數適合加virtural嗎2.4 C11 override 和 final2.5 三個概念的對比 3 多態的原理4 抽象類4.1 概念4.2 純虛函數 1 多態的概念 多態的概念&#xff1a;通俗來說&#xff0c;就是…

2413. 最小偶倍數

題目&#xff1a; 給你一個正整數 n &#xff0c;返回 2 和 n 的最小公倍數&#xff08;正整數&#xff09;。 示例 1&#xff1a; 輸入&#xff1a;n 5 輸出&#xff1a;10 解釋&#xff1a;5 和 2 的最小公倍數是 10 。 示例 2&#xff1a; 輸入&#xff1a;n 6 輸出&a…

JS 手寫 節流throttle 防抖debounce函數

防抖debounce // 手寫防抖 function debounce(fn, delay 200) {// timer 在閉包中let timer null// 返回一個函數return function(...args) {if (timer) {clearTimeout(timer) // 清空上次的值}timer setTimeout(() > {fn.apply(this, args) // 透傳 this 和函數參數},…

【再探】設計模式—代理模式

代理是指授權代理人在一定范圍內代表其向第三方進行處理有關事務。 1 代理模式 需求&#xff1a;1&#xff09;將業務代碼與非業務代碼分離&#xff0c;在不改變代碼結構的基礎上&#xff0c;為其添加新的功能。2&#xff09;為系統中的某些操作做同一處理&#xff0c;例如進…

[實例] Unity Shader 逐像素漫反射與半蘭伯特光照

漫反射光照是Unity中最基本最簡單的光照模型&#xff0c;本篇將會介紹在片元著色器中實現反射效果&#xff0c;并會采用半蘭伯特光照技術對其進行改進。 1. 逐頂點光照與逐像素光照 在Unity Shader中&#xff0c;我們可以有兩個地方可以用來計算光照&#xff1a;在頂點著色器…

數據結構:帶頭雙向循環鏈表

目錄 前言 鏈表實現 1.定義節點 2.接口實現 1.開辟新節點 2.初始化 3.打印鏈表 4.添加節點 頭插 尾插 在pos位置之前增加節點 5.刪除節點 判空 頭刪 尾刪 刪除pos位置的節點 6.查找 7.釋放 前言 帶頭雙向循環鏈表的結構最復雜&#xff0c;一般用在單獨存儲數…

z3-加法器實驗

補碼器加減法&#xff0c;運算方法簡介 我們要知道什么是補碼的加法&#xff0c;我們為什么要用補碼的加法&#xff1f; 補碼的加法其實就是將兩個補碼形式的二進制數字直接相加&#xff0c;處理的時候忽略超出固定位數的進位。補碼的加法運算和無符號二進制數的加法操作一樣&…

【最新區塊鏈論文錄用資訊】CCF A — SP 2024 共17篇

Conference&#xff1a;45th IEEE Symposium onSecurity and Privacy CCF level&#xff1a;CCF A Categories&#xff1a;網絡與信息安全 Year&#xff1a;2024 Num&#xff1a;17 Efficient Zero-Knowledge Arguments For Paillier Cryptosystem Paillier 加密系統的有效…