普通函数的参数中的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.mzph.cn/web/14973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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 : […

JAVA云HIS医院系统源码 HIS源码:云HIS系统与SaaS的关系

云HIS系统与SaaS的关系 云HIS系统是一种基于云计算技术的医院信息系统&#xff0c;它采用B/S架构&#xff0c;通过云端SaaS服务的方式提供。用户可以通过浏览器访问云HIS系统&#xff0c;无需关注系统的部署、维护、升级等问题。云HIS系统通常具有模板化、配置化、智能化等特点…

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;就是…

【再探】设计模式—代理模式

代理是指授权代理人在一定范围内代表其向第三方进行处理有关事务。 1 代理模式 需求&#xff1a;1&#xff09;将业务代码与非业务代码分离&#xff0c;在不改变代码结构的基础上&#xff0c;为其添加新的功能。2&#xff09;为系统中的某些操作做同一处理&#xff0c;例如进…

[实例] Unity Shader 逐像素漫反射与半兰伯特光照

漫反射光照是Unity中最基本最简单的光照模型&#xff0c;本篇将会介绍在片元着色器中实现反射效果&#xff0c;并会采用半兰伯特光照技术对其进行改进。 1. 逐顶点光照与逐像素光照 在Unity Shader中&#xff0c;我们可以有两个地方可以用来计算光照&#xff1a;在顶点着色器…

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 加密系统的有效…

基于python的网页自动刷新工具

1.下载webdriver https://msedgewebdriverstorage.z22.web.core.windows.net/?prefix122.0.2365.59/下载Edge的浏览器驱动 2.安装selenium pip install selenium4.11.1 3.写代码 # -*- coding: utf-8 -*- import tkinter as tk from tkinter import messagebox import thr…

【halcon】set_part 实现平移和缩放 彻悟版

背景 之前写了一篇关于set_part 的文章 &#xff0c;确实也实现了平移和缩放。平移是对的&#xff0c;但是缩放其实有畸变。这个问题一直都困扰着我&#xff0c;知道昨天连续测试了好几个小时&#xff0c;直到晚上11点终于完美解决。 坐标和高宽 坐标 再讲set_part 之前&am…

免费撸gpt-4o和各种大模型实用经验分享

项目 Github: https://github.com/MartialBE/one-api 先贴两张图&#xff1a; 说明 免费撸AI大模型,各位可以对照下面我给出的大模型记录表来填&#xff0c;key需要自己去拿&#xff0c;国内都需要手机号验证&#xff0c;如果你不介意。另外我在自己的博客放出免费API给大家…

模型评价指标笔记:混淆矩阵+F1+PR曲线+mAP

评价指标 二分类评价指标 混淆矩阵 TP: 正确预测为了正样本&#xff0c;原来也是正样本 FN: 错误的预测为负样本&#xff0c;原来是正样本 (漏报&#xff0c;没有找到正确匹配的数目) FP: 错误的预测为正样本&#xff0c;原来是负样本 (误报&#xff0c;没有的匹配不正确) TN…

CIM模型

CIM 是 Esri 制图信息模型。 它是一个地图内容规范,用于记录在保存、读取、引用或打开时如何永久保留描述不同项目组件的信息。 该规范以 JSON 表示,适用于 ArcGIS 应用程序和 API 中的地图、场景、布局、图层、符号和样式。 CIM 不仅限于制图设置。 要了解属性的组织方式以及…

【Tools】SpringBoot工程中,对于时间属性从后端返回到前端的格式问题

Catalog 时间属性格式问题一、需求二、怎么使用 时间属性格式问题 一、需求 对于表中时间字段&#xff0c;后端创建对应的实体类的时间属性需要设定格式&#xff08;默认的格式不方便阅读&#xff09;&#xff0c;再返回给前端。 二、怎么使用 导入jackson相关的坐标&#x…

Vue.js - Vue 的安装 以及 常用的 Vue 指令 【0基础向 Vue 基础学习】

文章目录 Vue 快速上手1、Vue.js 官网 & Vue.js 的获取2、创建 Vue 实例&#xff0c;初始化渲染3、插值表达式 安装 Vue 开发者工具&#xff1a;装插件调试 Vue 应用Vue 指令1、v-show 指令2、v-if3、v-else & v-else-if4、v-onv-on 调用传参 5、v-bindv-bind 对于样式…