普通函数的参数中的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 int

print<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 declaration

class 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 declaration

class 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) const
    void 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 int

foo<std::string>("hello", 42); // x has type std::string, y has type int

foo<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 parameters

requires 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, 选择第二个声明

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/643588.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java图书电子商务网站的设计与实现源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的图书电子商务网站的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 图书电子商…

推荐一款自助分析的财务分析软件:奥威BI软件

奥威BI软件是一款支持多维度动态自助分析的软件&#xff0c;预设了智能财务分析方案&#xff0c;提供内存行列计算模型解决财务指标计算难题&#xff0c;界面简洁&#xff0c;以点击、拖曳操作为主&#xff0c;十分适合没有IT背景的财务人做财务分析。因此也经常有人说奥威BI软…

tcp协议介绍,协议段格式(端口号,首部长度,窗口大小,序号,确认序号,6个标志位),流量控制,确认应答机制,捎带应答,三次握手的双方认知不一致问题

目录 tcp协议 介绍 传输控制协议 图解 全双工 缓冲区 控制 tcp协议段格式 数据在不同层的名称 图解 ​编辑 端口号 首部长度 窗口大小 -- 引入 前提 流量控制 确认应答机制 窗口大小 -- 介绍 序号 -- 引入 确认应答机制的进一步探讨 如果应答丢失 捎带应…

基于Android studio 使用SQLite数据库完成登录注册功能——保姆级教程

&#x1f345;文章末尾有获取完整项目源码方式&#x1f345; 点击快捷传送地址&#xff1a; 保姆级教学——制作登陆注册功能页面 目录 一、准备工作 二、创建相关文件 三、页面布局 四、DabaHelper帮助类的编写 五、RegisterActivity注册页面 六、LoginActivity登录页面…

clion读取文件设置为读取当前目录下的文件

1.问题 使用vs读取文件时一切正常 但是同样的代码在clion中无法正常执行 原因 原因&#xff1a;clion的源文件找不到input.txt文件的位置 需要设置工作目录&#xff0c;例如此时input.txt在当前目录下&#xff0c;那么就设置 设置当前文件的工作目录为$FileDir$即可&am…

通过 NIO + 多线程 提升硬件设备与系统的数据传输性能

一、项目展示 下图&#xff08;模拟的数据可视化大屏&#xff09;中数据是动态显示的 二、项目简介 描述&#xff1a;使用Client模拟了硬件设备&#xff0c;比如可燃气体浓度检测器。Client通过Socket与Server建立连接&#xff0c;Server保存数据到txt文件&#xff0c;并使用W…

操作系统总结4----死锁的处理策略总结

目录 2.4.2 死锁的处理策略-----预防死锁 &#xff08;1&#xff09;知识总览 &#xff08;2&#xff09;破环互斥条件 &#xff08;3&#xff09;破环不剥夺条件 &#xff08;4&#xff09;破环求情和保持条件 &#xff08;5&#xff09;破环循环等待条件 总结 2.4.3 死…

广告圈策划大师课:活动策划到品牌企划的深度解析

对于刚接触营销策划的新人来说&#xff0c;在这个知识密集型行业里生存&#xff0c;要学习非常多各种意思相近的概念&#xff0c;常常让人感到头疼&#xff0c;难以区分。 这里对这些策划概念进行深入解析&#xff0c;帮助您轻松理清各自的含义和区别。 1. 活动策划&#xff…

操作抖音小店一直不出单怎么办?只需要做好这两点就可以了!

大家好&#xff0c;我是电商小V 最近很多新手小伙伴来咨询我说自己操作抖音小店&#xff0c;自己的店铺长时间不出单应该怎么办&#xff1f;今天咱们就来详细的说一下&#xff0c; 咱们要清楚的就是自己的店铺不出&#xff0c;只需要咱们做好这两点就可以了&#xff0c; 第一点…

华为机考入门python3--(29)牛客29-字符串加解密

分类&#xff1a;字符变换 知识点&#xff1a; 字符是字母 char.isalpha() 字符是小写字母 char.islower() 字符是数字 char.isdigit() b变C chr((ord(b) - ord(a) 1) % 26 ord(A)) 题目来自【牛客】 # 加密 def encrypt_string(s):result ""for ch…

C++的AVL树

目录 基本概念 插入的语言分析 LL右旋 RR左旋 额外结论及问题1 LR左右旋 RL右左旋 额外结论及问题2 插入结点 更新bf与判断旋转方式 旋转代码实现 准备工作一 LL右旋的实现 RR左旋的实现 准备工作二 LR左右旋的实现 RL右左旋的实现 完整代码 基本概念 1、…

抖音小店新规重磅来袭!事关店铺流量!商家的福音来了?

大家好&#xff0c;我是喷火龙。 就在前两天&#xff0c;抖店发布了新规&#xff0c;我给大家总结了一下&#xff0c;无非就是两点。 第一点&#xff1a;保证金下调&#xff0c;一证开多店。 第二点&#xff1a;新品上架破10单&#xff0c;有流量扶持。 咱来细细的解读&…

无人机助力光伏项目测绘建模

随着全球对可再生能源需求的不断增长&#xff0c;光伏项目作为其中的重要一环&#xff0c;其建设规模和速度都在不断提高。在这一背景下&#xff0c;如何高效、准确地完成光伏项目的测绘与建模工作&#xff0c;成为了行业发展的重要课题。近年来&#xff0c;无人机技术的快速发…

【产品经理】如何培养对市场的洞察力

引言&#xff1a;        在最近频繁的产品管理职位面试中&#xff0c;我深刻体会到了作为产品经理需要的不仅仅是对市场和技术的敏锐洞察&#xff0c;更多的是在复杂多变的环境中&#xff0c;如何运用沟通、领导力和决策能力来引导产品从概念走向市场。这一系列博客将分享…

技术驱动未来,全面揭秘 Sui 的生态发展和布局

在不到一年的时间里&#xff0c;由 Mysten Labs 团队创立的 Layer1 区块链 Sui 迅速崛起&#xff0c;成功跃升至去中心化金融&#xff08;DeFi&#xff09;的前十名。根据 DeFi Llama 的数据&#xff0c;Sui的总锁定价值&#xff08;TVL&#xff09;在短短四个月内增长超过 100…

堆的实现

前言&#xff1a;本文讲述堆实现的几个难点&#xff0c;注意本文主要是以实现为主&#xff0c;建议有些基本概念认识的人阅读。 目录 1.堆 2.堆的实现 堆结构的定义&#xff1a; 要实现的接口&#xff1a; 接口的实现&#xff1a; 堆的初始化和销毁&#xff1a; 向堆中插…

【简单介绍下链表基础知识】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

第八届能源、环境与材料科学国际学术会议(EEMS 2024)

文章目录 一、重要信息二、大会简介三、委员会四、征稿主题五、论文出版六、会议议程七、出版信息八、征稿编辑 一、重要信息 会议官网&#xff1a;http://ic-eems.com主办方&#xff1a;常州大学大会时间&#xff1a;2024年06月7-9日大会地点&#xff1a;新加坡 Holiday Inn …

OA界面这么香吗?总有老铁私信,让我多发点,他好参考。

OA的确是B端系统应用最为广泛的一种&#xff0c;这次再给大家分享十来个页面&#xff0c;希望对他们的界面提升有所帮助。 举报 评论 3

计算机考研|408开始的晚,怎么入手复习?六个月保姆级规划

万事开头难&#xff0c;特别是408 大家在第一遍复习408的时候&#xff0c;基本上都有这个问题&#xff0c;就是复习速度慢&#xff0c;理解成本高&#xff0c;因为数据结构&#xff0c;计算机组成原理这些都是大一大二开始学的内容&#xff0c;等到自己准备考研的时候&#xf…