using 声明

先来看 using 声明在类中的应用:

代码1

#include <iostream>

using namespace std;

struct A {
void f(int) {cout << "A::f(int)" << endl;}
}; struct S : A {
}; int main()
{
S s;
s.f(1); // A::f(int)
}
  • 类 S 继承了类 A 的成员函数 f,所以类 S 的实例 s 调用 f 输出为 A::f

代码2

#include <iostream>

using namespace std;

struct A {
void f(int) {cout << "A::f(int)" << endl;}
}; struct S : A {
void f(double) {cout << "S::f(double)" << endl;}
}; int main()
{
S s;
s.f(1); // S::f(double)
}
  • 类 S 继承了类 A 的成员函数 f,同时类 S 也定义了成员函数 f
  • 类 S 的成员函数 f 遮蔽了基类中的同名成员函数,所以 S 的实例 s 调用 f 输出为 S::f

代码3

#include <iostream>

using namespace std;

struct A {
void f(int) {cout << "A::f(int)" << endl;}
}; struct S : A {
using A::f;
void f(double) {cout << "S::f(double)" << endl;}
}; int main()
{
S s;
s.f(1); // A::f(int)
s.f(2.5); // S::f(double)
}
  • 类 S 继承了类 A 的成员函数 f,同时类 S 也定义了成员函数 f
  • 类 S 通过 using 声明将基类 A 的成员函数 f 引入自己的作用域。类 S 的成员函数 f 与基类 A 中的同名成员函数形成重载关系。
  • 参数为整型时,根据重载决策 S 的实例 s 调用 f 输出为 A::f
  • 参数为浮点型时,根据重载决策 S 的实例 s 调用 f 输出为 S::f

代码4

#include <iostream>

using namespace std;

struct A {
void f(int) {cout << "A::f(int)" << endl;}
}; struct B {
void f(double) {cout << "B::f(double)" << endl;}
}; struct S : A, B {
}; int main()
{
S s;
s.f(1); // compile error
s.f(2.5); // compile error
}
  • 类 S 同时继承了类 A 和类 B 的成员函数 f,两者形成竞争关系。
  • 编译器不能判断 S 的实例 s 所调用的成员函数 f 来自类 A 还是类 B,故编译出错。

代码5

#include <iostream>

using namespace std;

struct A {
void f(int) {cout << "A::f(int)" << endl;}
}; struct B {
void f(double) {cout << "B::f(double)" << endl;}
}; struct S : A, B {
using A::f;
using B::f;
}; int main()
{
S s;
s.f(1); // A::f(int)
s.f(2.5); // B::f(double)
}
  • 类 S 同时继承了类 A 和类 B 的成员函数 f。
  • 类 S 通过 using 声明将基类 A 和基类 B 的成员函数 f 都引入自己的作用域。基类 A 和基类 B 的同名成员函数在类 S 中形成重载关系。
  • 参数为整型,根据重载决策 S 的实例 s 调用 f 输出为 A::f
  • 参数为浮点型时,根据重载决策 S 的实例 s 调用 f 输出为 B::f

C++17的 using 声明

在 C++17 中多个 using 声明可以通过逗号连接起来,合成一个 using 声明。

代码6

#include <iostream>

using namespace std;

struct A {
void f(int) {cout << "A::f(int)" << endl;}
}; struct B {
void f(double) {cout << "B::f(double)" << endl;}
}; struct S : A, B {
using A::f, B::f; // C++17
}; int main()
{
S s;
s.f(1); // A::f(int)
s.f(2.5); // B::f(double)
}

C++17的变长 using 声明

通过使用加了 ... 的 using 声明,可以将变长模板参数类型中的 using 声明转化为由多个用逗号合成的变长 using 声明。

#include <iostream>
#include <string> using namespace std; template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; int main()
{
overloaded s{
[](int){cout << "int" << endl;},
[](double){cout << "double" << endl;},
[](string){cout << "string" << endl;},
};
s(1); // int
s(1.); // double
s("1"); // string
}
  • template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };

    这是一个类模板的声明。
  • template<class... Ts>:overloaded 类的模板参数为可变长的参数包 Ts。

    假设 Ts 包含 T1, T2, ... , TN,那么这一句声明可以展开为:template<class T1, class T2, ..., class TN>
  • struct overloaded : Ts...:overloaded 类的基类为参数包 Ts 内所有的参数类型。

    假设 Ts 包含 T1, T2, ... , TN,那么这一句声明可以展开为:struct overloaded : T1, T2, ..., TN
  • using Ts::operator()...;:这是一个变长 using 声明。

    假设 Ts 包含 T1, T2, ... , TN,那么这一句声明可以展开为:using T1::operator(), T1::operator(), ..., TN::operator();

    也就是说,overloaded 类的基类即参数包 Ts 内所有的参数类型的函数调用操作符均被 overloaded 类引入了自己的作用域。
  • template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

    这是一个自动推断向导,用于帮助编译器根据 overloaded 构造器参数的类型来推导 overloaded 的模板参数类型。

    这个自动推断向导告诉编译器,如果 overloaded 构造器所有参数的类型的集合为Ts,那么 overloaded 的模板参数类型就是 Ts 所包含的所有类型。

    也就是说如果表达式 a1, a2, ..., an 的类型分别为 T1, T2, ..., TN,

    那么构造器表达式 overloaded {a1, a2, ..., an} 的类型就是 overloaded<T1, T2, ..., TN>

    * overloaded s{

    [](int){cout << "int" << endl;},

    [](double){cout << "double" << endl;},

    [](string){cout << "string" << endl;},

    };

    overloaded 类的实例 s 的构造器包含3个lambda参数,也可以看作3个各自包含一个 operator() 的函数对象。

    根据 overloaded 类的定义,s 对象将继承这3个lambda(函数对象)的 operator() ,也就是说这3个lambda的 operator() 即函数体在 s 对象内部形成重载关系。

    根据 overloaded 类的自动推断向导,s 对象的类型为overloaded<T1, T2, T3>,其中T1, T2, T3为3个lambda参数的类型。
  • 通过利用 C++17 的新特性变长的 using 声明以及自动推断向导,overloaded类的实例可以简洁并且巧妙地将多个lambda合成一个大的具有多个相互重载的 operator() 的函数对象。
  • overloaded 这个类模板如此有用,实现机制又如此精妙,实在是应该早日纳入标准库中。

C++17尝鲜:变长 using 声明的更多相关文章

  1. C++17尝鲜

    https://cloud.tencent.com/developer/article/1351910 [译]C++17,optional, any, 和 variant 的更多细节 用户261520 ...

  2. C++17尝鲜:variant

    variant variant 是 C++17 所提供的变体类型.variant<X, Y, Z> 是可存放 X, Y, Z 这三种类型数据的变体类型. 与C语言中传统的 union 类型 ...

  3. C++17尝鲜:结构化绑定声明(Structured Binding Declaration)

    结构化绑定声明 结构化绑定声明,是指在一次声明中同时引入多个变量,同时绑定初始化表达式的各个子对象的语法形式. 结构化绑定声明使用auto来声明多个变量,所有变量都必须用中括号括起来. cv-auto ...

  4. C++17尝鲜:在 if 和 switch 语句中进行初始化

    初始化语句 在C++17中,类似于 for 语句,在 if 和 switch 语句的判断条件之前也能加上初始化语句,语法形式如下: if (初始化语句; 条件) 语句 else 语句 switch ( ...

  5. C++17尝鲜:编译期 if 语句

    Constexpr If(编译期 if 语句) 以 if constexpr 打头的 if 语句被称为 Constexpr If. Constexpr If 是C++17所引入的新的语法特性.它为C+ ...

  6. C++17尝鲜:string_view

    string_view string_view 是C++17所提供的用于处理只读字符串的轻量对象.这里后缀 view 的意思是只读的视图. 通过调用 string_view 构造器可将字符串转换为 s ...

  7. C++17尝鲜:类模板中的模板参数自动推导

    模板参数自动推导 在C++17之前,类模板构造器的模板参数是不能像函数模板的模板参数那样被自动推导的,比如我们无法写 std::pair a{1, "a"s}; // C++17 ...

  8. PL/SQL — 变长数组

    PL/SQL变长数组是PL/SQL集合数据类型中的一种,其使用方法与PL/SQL嵌套表大同小异,唯一的区别则是变长数组的元素的最大个数是有限制的.也即是说变长数组的下标固定下限等于1,上限可以扩展.下 ...

  9. C99新增内容之变长数组(VLA)

    我们在使用多维数组是有一点,任何情况下只能省略第一维的长度.比如在函数中要传一个数组时,数组的行可以在函数调用时传递,当属数组的列却只能在能被预置在函数内部.看下面一个例子: #define COLS ...

随机推荐

  1. 在MySQL中实现Rank高级排名函数

    MySQL中没有Rank排名函数,当我们需要查询排名时,只能使用MySQL数据库中的基本查询语句来查询普通排名.尽管如此,可不要小瞧基础而简单的查询语句,我们可以利用其来达到Rank函数一样的高级排名 ...

  2. 去掉user agent stylesheet 浏览器默认样式 [ 2.0 版本 ]

    今天在写一个网页的时候发现一个问题,我的table的样式很奇怪,也没有设置什么样式,跟其他的页面不一样,打开开发者工具一看,发现有这么点样式: 其中右上角:user agent stylesheet ...

  3. 初始化ha环境下的hadoop的hdfs

    1)启动zookeeper2)启动所有的journalnode[hadoop@datanode1 ~]$ ~/hadoop-2.7.3/sbin/hadoop-daemon.sh start jour ...

  4. Linux 下 MQ 的安装

    在WebSphere MQ 7.1版本以前,同时只能有一个产品的安装实例,在UNIX和Linux系统上,/usr/lib,/usr/bin和/usr/include目录下会增加一些软连接,也指向了这个 ...

  5. Android 获取手机内部信息,内核版本、基带版本、内部版本等

    TextView text = (TextView) findViewById(R.id.textView1); String phoneInfo = "Product: " + ...

  6. 使用UtraISO为U盘制作系统启动盘

    安装最新的Ubuntu18.04操作系统: 1.在utraiso软件中打开系统的iso文件: 2.插入U盘: 3.点击[启动]选项卡,选择[写入硬盘映像]: 4.最关键的一步: 刻录校验:打上对勾: ...

  7. Servlet(API)生命周期

    一.最上层接口Servlet 查看Servlet接口源码: 有5个方法 访问过程(默认): 1.进行Servlet类加载 当Tomcat容器启动后,服务器寻找应用部署的描述文件(web.xml),从部 ...

  8. (转)SQL知识_SQL Case when 的使用方法

    原文地址:http://www.cnblogs.com/yazdao/archive/2009/12/09/1620482.html Case具有两种格式.简单Case函数和Case搜索函数. --简 ...

  9. 流(Stream)与文件流(FileStream)

    //通过流的方式添加 StreamWriter writer = new StreamWriter(@"C:\A\ca.txt", true, Encoding.Default); ...

  10. 字符串切分 String.Split 和 Regex.Split(小技巧)

    当切割字符串的是单个字符时可使用String.Split string strSample="ProductID:20150215,Categroy:Food,Price:15.00&quo ...