Какая логика стоит за ключевым словом «using» в C ++?

145

Какая логика стоит за ключевым словом «using» в C ++?

Он используется в разных ситуациях, и я пытаюсь выяснить, есть ли у них что-то общее, и есть причина, по которой ключевое слово «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
user3111311
источник
53
Стандартный комитет ненавидит ввод новых ключевых слов в грамматику C ++.
Интернет сделан из catz 26.12.13
4
@tehinternetsismadeofcatz Если это действительно логика, извините, я пойду и убью себя сейчас.
user3111311
62
@ user3111311: Вы понимаете последствия введения новых зарезервированных слов, верно? Это означает, что весь существующий код, который использовал их в качестве имен идентификаторов, неожиданно не скомпилируется. Это плохая вещь. Например, есть много кода на C, который не может быть скомпилирован как C ++, потому что он содержит такие вещи, как int class;. Было бы еще хуже, если бы код C ++ внезапно перестал быть действительным C ++.
Бен Фойгт
7
@BenVoigt: тот факт, что использование кода C int class;не будет компилироваться как C ++, не совсем плохая вещь. Его можно использовать для гарантии того, что код C будет скомпилирован как C. Слишком легко забыть, что C и C ++ - это два разных языка - и, фактически, есть код, который является допустимым C и допустимым C ++, но с другой семантикой.
Кит Томпсон
1
В этом отношении usingне хуже (или лучше), чем static. ИМХО, смысл не вводить новые ключевые слова очень важен, поскольку, как объясняют интернет-специалисты, сделан из catz и Ben Voigt.
Кассио Нери

Ответы:

114

В C ++ 11 usingключевое слово при использовании для type aliasидентично typedef.

7.1.3.2

Typedef-name также может быть введено объявлением псевдонима. Идентификатор, следующий за ключевым словом using, становится typedef-name, а необязательный атрибут-identifier-seq, следующий за идентификатором, относится к этому typedef-name. Он имеет такую ​​же семантику, как если бы он был введен спецификатором typedef. В частности, он не определяет новый тип и не должен появляться в идентификаторе типа.

Бьярне Страуструп дает практический пример:

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

До C ++ 11 usingключевое слово может привести функции-члены в область видимости. В C ++ 11 теперь вы можете сделать это для конструкторов (другой пример Бьярна Страуструпа):

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) 
    // ...
}; 

Бен Войт приводит довольно вескую причину отказа от введения нового ключевого слова или нового синтаксиса. Стандарт хочет как можно больше не ломать старый код. Вот почему в предложении документов вы увидите разделы нравится Impact on the Standard, Design decisionsи как они могут повлиять на старый код. Бывают ситуации, когда предложение кажется действительно хорошей идеей, но может не иметь тяги, потому что его будет слишком сложно реализовать, слишком запутанно или будет противоречить старому коду.


Вот старая статья 2003 года №1449 . Похоже, обоснование связано с шаблонами. Предупреждение: возможны опечатки из-за копирования из 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

Обратите внимание, что мы специально избегаем термина «typedef template» и вводим новый синтаксис, включающий пару «using» и «=», чтобы избежать путаницы: мы не определяем здесь какие-либо типы, мы вводим синоним (то есть псевдоним) для абстракция идентификатора типа (т.е. выражения типа), включающего параметры шаблона. Если параметры шаблона используются в выводимых контекстах в выражении типа, то всякий раз, когда псевдоним шаблона используется для формирования идентификатора шаблона, можно определить значения соответствующих параметров шаблона - подробнее об этом будет сказано ниже. В любом случае, теперь возможно написать универсальные функции, которые работают Vec<T>в выводимом контексте, и синтаксис также улучшен. Например, мы можем переписать foo как:

template <typename T> void foo (Vec<T>&);

Здесь мы подчеркиваем, что одной из основных причин для предложения псевдонимов шаблонов было то, что вывод аргумента и призыв к 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] > > - где маркер является заполнителем для имени типа. Следовательно, мы не предлагаем синтаксис «typedef». С другой стороны предложение

template<class T>
    using Vec = std::vector<T, MyAllocator<T> >;

можно читать / интерпретировать как: теперь я буду использовать Vec<T>как синоним для std::vector<T, MyAllocator<T> >. С этим чтением новый синтаксис для псевдонимов кажется достаточно логичным.

Я думаю, что здесь проводится важное различие, псевдоним es вместо типа 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);
Габриэль Стейплс
источник
2
@ user3111311 Какое еще ключевое слово вы имели в виду? "авто"? "регистр"?
Раймонд Чен
2
using P = [](double)->void;есть, AFAIK, недействительно C ++ 11. Это, однако,: using P = auto(double)->void;и создает тип функции (такой P*как указатель на функцию).
DYP
2
Его зовут Бьярне Страуструп;) (обратите внимание на второе число в Страуструпе)
дип
1
@RaymondChen: на самом деле registerне звучит так плохо, в:register X as Y
MFH
1
К сожалению, registerначинается объявление переменной, так что это уже имеет значение. Объявите переменную регистра с именем Y типа X.
Раймонд Чен,