C++中“using”关键字背后的逻辑是什么?
它用于不同的情况,我试图找出所有这些是否有共同点,以及为什么使用“using”关键字是有原因的。
using namespace std; // to import namespace in the current namespace using T = int; // type alias using SuperClass::X; // using super class methods in derived class
在 C++11 中,using关键字 fortype alias与typedef.
using
type alias
typedef
7.1.3.2
typedef-name 也可以由别名声明引入。using 关键字之后的标识符成为 typedef-name,标识符之后的可选属性说明符序列属于该 typedef-name。它具有与由 typedef 说明符引入的语义相同的语义。特别是,它没有定义新的类型,并且它不应出现在 type-id 中。
Bjarne Stroustrup 提供了一个实际示例:
typedef void (*PFD)(double); // C style typedef to make `PFD` a pointer to a function returning void and accepting double using PF = void (*)(double); // `using`-based equivalent of the typedef above using P = [](double)->void; // using plus suffix return type, syntax error using P = auto(double)->void // Fixed thanks to DyP
在 C11 之前,using关键字可以将成员函数带入作用域。在 C11 中,您现在可以对构造函数执行此操作(另一个 Bjarne Stroustrup 示例):
class Derived : public Base { public: using Base::f; // lift Base's f into Derived's scope -- works in C++98 void f(char); // provide a new f void f(int); // prefer this f to Base::f(int) using Base::Base; // lift Base constructors Derived's scope -- C++11 only Derived(char); // provide a new constructor Derived(int); // prefer this constructor to Base::Base(int) // ... };
Ben Voight 为不引入新关键字或新语法的基本原理提供了一个很好的理由。该标准希望尽可能避免破坏旧代码。这就是为什么在提案文档中您会看到类似Impact on the Standard、的部分Design decisions以及它们如何影响旧代码的原因。在某些情况下,提案似乎是一个非常好的想法,但可能没有吸引力,因为它太难实现、太混乱或与旧代码相矛盾。
Impact on the Standard
Design decisions
这是 2003 n1449的旧论文。理由似乎与模板有关。警告:由于从 PDF 复制过来,可能会出现拼写错误。
首先让我们考虑一个玩具示例: template <typename T> class MyAlloc {/*...*/}; template <typename T, class A> class MyVector {/*...*/}; template <typename T> struct Vec { typedef MyVector<T, MyAlloc<T> > type; }; Vec<int>::type p; // sample usage 这个习语的根本问题,也是这个提议的主要动机,是这个习语导致模板参数出现在不可演绎的上下文中。也就是说,如果不显式指定模板参数,就不可能调用下面的函数 foo。 template <typename T> void foo (Vec<T>::type&); 所以,语法有点难看。我们宁愿避免嵌套的::type 我们更喜欢以下内容: template <typename T> using Vec = MyVector<T, MyAlloc<T> >; //defined in section 2 below Vec<int> p; // sample usage 请注意,我们特别避免使用术语“ypedef 模板”,并引入涉及“sing”和“=涡”对的新语法,以帮助避免混淆:我们在这里没有定义任何类型,而是引入了同义词(即别名)用于涉及模板参数的类型标识(即类型表达式)的抽象。如果模板参数在类型表达式的可推导上下文中使用,那么无论何时使用模板别名来形成模板 ID,都可以推导出相应模板参数的值——更多内容将在后面介绍。在任何情况下,现在都可以编写Vec<T>在可推导上下文中运行的通用函数,并且语法也得到了改进。例如,我们可以将 foo 重写为: template <typename T> void foo (Vec<T>&); 我们在这里强调,提出模板别名的主要原因之一是参数推导和调用foo(p) 将成功。
首先让我们考虑一个玩具示例:
template <typename T> class MyAlloc {/*...*/}; template <typename T, class A> class MyVector {/*...*/}; template <typename T> struct Vec { typedef MyVector<T, MyAlloc<T> > type; }; Vec<int>::type p; // sample usage
这个习语的根本问题,也是这个提议的主要动机,是这个习语导致模板参数出现在不可演绎的上下文中。也就是说,如果不显式指定模板参数,就不可能调用下面的函数 foo。
template <typename T> void foo (Vec<T>::type&);
所以,语法有点难看。我们宁愿避免嵌套的::type 我们更喜欢以下内容:
::type
template <typename T> using Vec = MyVector<T, MyAlloc<T> >; //defined in section 2 below Vec<int> p; // sample usage
请注意,我们特别避免使用术语“ypedef 模板”,并引入涉及“sing”和“=涡”对的新语法,以帮助避免混淆:我们在这里没有定义任何类型,而是引入了同义词(即别名)用于涉及模板参数的类型标识(即类型表达式)的抽象。如果模板参数在类型表达式的可推导上下文中使用,那么无论何时使用模板别名来形成模板 ID,都可以推导出相应模板参数的值——更多内容将在后面介绍。在任何情况下,现在都可以编写Vec<T>在可推导上下文中运行的通用函数,并且语法也得到了改进。例如,我们可以将 foo 重写为:
Vec<T>
template <typename T> void foo (Vec<T>&);
我们在这里强调,提出模板别名的主要原因之一是参数推导和调用foo(p) 将成功。
foo(p)
后续论文n1489解释了为什么using不使用typedef:
有人建议(重新)使用关键字 typedef ——正如论文 [4] 中所做的那样——引入模板别名: template<class T> typedef std::vector<T, MyAllocator<T> > Vec; 该表示法的优点是使用已知的关键字来引入类型别名。但是,它也显示了几个缺点,其中在别名不是指定类型而是模板的上下文中使用已知的关键字为类型名称引入别名会造成混淆;Vec不是类型的别名,不应作为 typedef-name。名称Vec是族的名称std::vector< [bullet] , MyAllocator< [bullet] > > ——其中项目符号是类型名称的占位符。因此,我们不建议使用“ypedef”语法。另一方面,句子 template<class T> using Vec = std::vector<T, MyAllocator<T> >; 可以阅读/解释为:从现在开始,我将Vec<T>用作 . 的同义词`std::vector<T, MyAllocator `。通过这种阅读,别名的新语法似乎是合乎逻辑的。
有人建议(重新)使用关键字 typedef ——正如论文 [4] 中所做的那样——引入模板别名:
template<class T> typedef std::vector<T, MyAllocator<T> > Vec;
该表示法的优点是使用已知的关键字来引入类型别名。但是,它也显示了几个缺点,其中在别名不是指定类型而是模板的上下文中使用已知的关键字为类型名称引入别名会造成混淆;Vec不是类型的别名,不应作为 typedef-name。名称Vec是族的名称std::vector< [bullet] , MyAllocator< [bullet] > > ——其中项目符号是类型名称的占位符。因此,我们不建议使用“ypedef”语法。另一方面,句子
Vec
std::vector< [bullet] , MyAllocator< [bullet] > >
template<class T> using Vec = std::vector<T, MyAllocator<T> >;
可以阅读/解释为:从现在开始,我将Vec<T>用作 . 的同义词`std::vector<T, MyAllocator
`。通过这种阅读,别名的新语法似乎是合乎逻辑的。
我认为重要的区别就在这里, 别名 es 而不是 type s。同一文件中的另一个引述:
别名声明是声明,而不是定义。别名声明将名称引入声明区域,作为声明右侧指定的类型的别名。该提案的核心与类型名称别名有关,但该符号显然可以推广以提供命名空间别名的替代拼写或重载函数的命名集(进一步讨论参见第 2.3 节)。[ 我的笔记:该部分讨论了该语法的外观以及它不属于提案的原因。 ] 可以注意到,语法产生式别名声明在任何可以接受 typedef 声明或命名空间别名定义的地方都是可以接受的。
总结,对于角色using:
namespace PO = boost::program_options
using PO = ...
A typedef declaration can be viewed as a special case of non-template alias-declaration
namespace std
它 不能 用于:
int i; using r = i; // compile-error
而是这样做:
using r = decltype(i);
命名一组重载。
// bring cos into scope using std::cos; // invalid syntax using std::cos(double); // not allowed, instead use Bjarne Stroustrup function pointer alias example using test = std::cos(double);