using声明被扩展了,允许使用逗号分隔声明列表,允许在包扩展中使用声明。
例如,现在可以编程:
例1:
#include <iostream>
class Base
{
public:
void a();
void b();
void c();
};
class Derived : private Base
{
public:
using Base::a, Base::b, Base::c;
};
int main()
{
Base b;
Derived d;
return 0;
}
在c++ 17之前,需要使用三个不同的声明。
例2:
#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)
}
1. 使用可变using声明
逗号分隔的using声明使我们能够从基类的可变参数列表中派生所有相同类型的操作。这种技术的一个很酷的应用是创建和设置lambda重载。定义如下:
template<typename... Ts>
struct overload : Ts...
{
using Ts::operator()...;
};
template<typename... Ts>
overload(Ts...)->overload<Ts...>;
可以重载两个lambdas如下:
auto twice = overload {
[](std::string& s) { s += s; },
[](auto& v) { v *= 2; }
};
这里我们创建一个对象类型的重载,使用推导指南来推导lambda的类型作为模板类型重载的基类,并使用聚合初始化来使用闭包类型的复制构造函数初始化基类的子对象。然后,using声明使两个函数调用操作符都可用于类型重载。如果没有using声明,基类将会有两个相同成员函数操作符()的不同重载,这是不明确的。
因此,可以传递一个字符串它调用第一个重载,或者传递另一个类型,它调用第二个重载:
int i = 42;
twice(i);
std::cout << "i: " << i << '\n'; // prints: 84
std::string s = "hi";
twice(s);
std::cout << "s: " << s << '\n'; // prints: hihi
这种技术的一个应用是std::variant的访问。
例3:
#include <iostream>
#include <string>
template<typename... Ts>
struct overload : Ts...
{
using Ts::operator()...;
};
template<typename... Ts>
overload(Ts...)->overload<Ts...>;
auto twice = overload{
[](std::string& s) { s += s; },
[](int& v) { v *= 2; }
};
int main()
{
int i = 1;
std::string s{ "hi"};
twice(i);
twice(s);
std::cout << "i=" << i << ", s=" << s << std::endl;
return 0;
}
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 类的实例 twice 的构造器包含2个lambda参数,也可以看作2个各自包含一个 operator() 的函数对象。
- 根据 overloaded 类的定义,twice 对象将继承这2个lambda(函数对象)的 operator() ,也就是说这2个lambda的 operator() 即函数体在 twice 对象内部形成重载关系。
根据 overloaded 类的自动推断向导,twice 对象的类型为overloaded<T1, T2>
,其中T1, T2为2个lambda参数的类型。 - 通过利用 C++17 的新特性变长的 using 声明以及自动推断向导,overloaded类的实例可以简洁并且巧妙地将多个lambda合成一个大的具有多个相互重载的 operator() 的函数对象。
2. using声明继承可变构造函数
除了一些关于继承构造函数的说明外,现在还可以声明一个variadic类template Multi,它派生自每个传递类型的基类。
template<typename T>
class Base
{
T value{};
public:
Base()
{
...
}
Base(T v) : value{v}
{
...
}
...
};
template<typename... Types>
class Multi : private Base<Types>...
{
public:
// derive all constructors:
using Base<Types>::Base...;
...
};
使用所有基类构造函数的using声明,可以为每种类型派生相应的构造函数。现在,当为三种不同类型的值声明Multi<> type时:
using MultiISB = Multi<int,std::string,bool>;
可以使用每个基类对应的构造函数声明对象:
MultiISB m1 = 42;
MultiISB m2 = std::string("hello");
MultiISB m3 = true;
根据新的语言规则,每个初始化调用匹配基类的对应构造函数和所有其他基类的默认构造函数。因此,m1调用Base<int>的默认构造函数,m2调用Base<std::string>的string构造函数,以及Base<bool>的默认构造函数。
原则上,在Multi<>中也可以申明所有的赋值构造函数:
template<typename... Types>
class Multi : private Base<Types>...
{
...
// derive all assignment operators:
using Base<Types>::operator=...;
};
例4:
#include <iostream>
#include <string>
template<typename T>
class Base
{
T value{};
public:
Base()
{
if (std::is_same_v<T, int>)
{
std::cout << "Base::int" << std::endl;
}
else if (std::is_same_v < T, std::string>)
{
std::cout << "Base::string" << std::endl;
}
else if (std::is_same_v < T, bool>)
{
std::cout << "Base::bool" << std::endl;
}
}
Base(T v) : value{ v }
{
if (std::is_same_v < T, int>)
{
std::cout << "Base(T)::int" << std::endl;
}
else if (std::is_same_v < T, std::string>)
{
std::cout << "Base(T)::string" << std::endl;
}
else if (std::is_same_v < T, bool>)
{
std::cout << "Base(T)::bool" << std::endl;
}
}
};
template<typename... Types>
class Multi : private Base<Types>...
{
public:
// derive all constructors:
using Base<Types>::Base...;
// derive all assignment operators:
using Base<Types>::operator=...;
};
using MultiISB = Multi<int, std::string, bool>;
int main()
{
MultiISB m1 = 42;
MultiISB m2 = std::string("hello");
MultiISB m3 = true;
MultiISB m4 = std::string("world");
m2 = m4;
return 0;
}
运行结果: