Qual é a lógica por trás da palavra-chave "using" em C ++?

145

Qual é a lógica por trás da palavra-chave "using" em C ++?

É usado em diferentes situações e estou tentando descobrir se todos têm algo em comum e há uma razão pela qual a palavra-chave "using" é usada como tal.

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
fonte
53
O comitê padrão odeia introduzir novas palavras-chave na gramática C ++.
internets é feito de catz
4
@tehinternetsismadeofcatz Se essa é realmente a lógica, desculpe-me, irei me matar agora.
user3111311
62
@ user3111311: Você reconhece as implicações da introdução de novas palavras reservadas, certo? Isso significa que todo o código existente que os utilizava como nomes de identificador falha repentinamente na compilação. Isso é uma coisa ruim. Por exemplo, há muito código C que não pode ser compilado como C ++ porque contém coisas como int class;. Seria ainda pior se o código C ++ de repente parasse de ser válido em C ++.
precisa
7
@BenVoigt: O fato de o código C usar int class;não ser compilado como C ++ não é totalmente ruim. Ele pode ser usado para garantir que o código C seja compilado como C. É muito fácil esquecer que C e C ++ são duas linguagens diferentes - e, na prática, existe um código que é C e C ++ válidos, mas com semânticas diferentes.
Keith Thompson
1
A esse respeito, usingnão é pior (ou melhor) que static. IMHO, o ponto de não introduzir novas palavras-chave é muito importante, conforme explicado pelas páginas internas: catz e Ben Voigt.
Cassio Neri

Respostas:

114

No C ++ 11, a usingpalavra - chave quando usada para type aliasé idêntica a typedef.

7.1.3.2

Um typedef-name também pode ser introduzido por uma declaração de alias. O identificador após a palavra-chave using se torna um typedef-name e o atributo-specifier-seq opcional após o identificador pertence a esse typedef-name. Tem a mesma semântica como se tivesse sido introduzida pelo especificador typedef. Em particular, ele não define um novo tipo e não deve aparecer no ID do tipo.

Bjarne Stroustrup fornece um exemplo prático:

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

Antes do C ++ 11, a usingpalavra - chave pode colocar funções de membro no escopo. No C ++ 11, agora você pode fazer isso para construtores (outro exemplo de 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 fornece uma boa razão por trás da lógica de não introduzir uma nova palavra-chave ou nova sintaxe. O padrão quer evitar a quebra de código antigo, tanto quanto possível. É por isso que nos documentos da proposta você vai ver secções gosta Impact on the Standard, Design decisionse como elas podem afetar código antigo. Há situações em que uma proposta parece realmente uma boa idéia, mas pode não ter tração, porque seria muito difícil de implementar, muito confuso ou contradizia o código antigo.


Aqui está um artigo antigo de 2003 n1449 . A lógica parece estar relacionada aos modelos. Aviso: pode haver erros de digitação devido à cópia do PDF.

Primeiro, vamos considerar um exemplo de brinquedo:

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

O problema fundamental com esse idioma, e o principal fato motivador dessa proposta, é que o idioma faz com que os parâmetros do modelo apareçam em um contexto não dedutível. Ou seja, não será possível chamar a função foo abaixo sem especificar explicitamente os argumentos do modelo.

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

Portanto, a sintaxe é um pouco feia. ::type Preferimos evitar o aninhado . Preferimos algo como o seguinte:

template <typename T>
using Vec = MyVector<T, MyAlloc<T> >; //defined in section 2 below
Vec<int> p; // sample usage

Observe que evitamos especificamente o termo "modelo typedef" e introduzimos a nova sintaxe que envolve o par "using" e "=" para ajudar a evitar confusões: não estamos definindo nenhum tipo aqui, estamos introduzindo um sinônimo (ou seja, alias) para uma abstração de uma identificação de tipo (ou seja, expressão de tipo) envolvendo parâmetros de modelo. Se os parâmetros do modelo forem usados ​​em contextos dedutíveis na expressão de tipo, sempre que o alias do modelo for usado para formar um ID do modelo, os valores dos parâmetros correspondentes do modelo poderão ser deduzidos - mais sobre isso se seguirá. De qualquer forma, agora é possível escrever funções genéricas que operam Vec<T>em contexto dedutível, e a sintaxe também é aprimorada. Por exemplo, poderíamos reescrever foo como:

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

Ressaltamos aqui que uma das principais razões para propor aliases de modelo foi para que a dedução de argumentos e a chamada para foo(p) tenham êxito.


O documento de acompanhamento n1489 explica por que, em usingvez de usar typedef:

Foi sugerido (re) usar a palavra-chave typedef - como foi feito no artigo [4] - para introduzir aliases de modelo:

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

Essa notação tem a vantagem de usar uma palavra-chave já conhecida por introduzir um alias de tipo. No entanto, também exibe várias desvantagens, entre as quais a confusão do uso de uma palavra-chave conhecida por introduzir um alias para um nome de tipo em um contexto em que o alias não designa um tipo, mas um modelo; Vecnão é um alias para um tipo e não deve ser usado para um typedef-name. O nome Vecé um nome para a família std::vector< [bullet] , MyAllocator< [bullet] > > - onde o marcador é um espaço reservado para um nome de tipo. Consequentemente, não propomos a sintaxe "typedef". Por outro lado, a sentença

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

pode ser lido / interpretado como: a partir de agora, usarei Vec<T>como sinônimo std::vector<T, MyAllocator<T> >. Com essa leitura, a nova sintaxe para aliasing parece razoavelmente lógica.

Eu acho que a distinção importante é feita aqui, aliás es em vez de tipos s. Outra citação do mesmo documento:

Uma declaração de alias é uma declaração e não uma definição. Uma declaração de alias introduz um nome em uma região declarativa como um alias para o tipo designado pelo lado direito da declaração. O núcleo desta proposta diz respeito a aliases de nomes de tipos, mas a notação pode obviamente ser generalizada para fornecer grafias alternativas de alias de namespace ou conjunto de nomes de funções sobrecarregadas (consulte a seção 2.3 para discussão adicional). [ Minha observação: essa seção discute como essa sintaxe pode ser e os motivos pelos quais ela não faz parte da proposta. ] Pode-se notar que a declaração de alias de produção gramatical é aceitável em qualquer lugar que uma declaração typedef ou uma definição de alias de namespace é aceitável.

Resumo, para o papel de using:

  • aliases de modelo (ou typedefs de modelo, o primeiro é o preferido)
  • alias de namespace (ou seja, namespace PO = boost::program_optionse using PO = ...equivalente)
  • o documento diz A typedef declaration can be viewed as a special case of non-template alias-declaration. É uma mudança estética e é considerada idêntica neste caso.
  • trazer algo para o escopo (por exemplo, namespace stdpara o escopo global), funções-membro, herdar construtores

Não pode ser utilizado para:

int i;
using r = i; // compile-error

Em vez disso, faça:

using r = decltype(i);

Nomeando um conjunto de sobrecargas.

// 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);
Gabriel Staples
fonte
2
@ user3111311 Que outra palavra-chave você tinha em mente? "auto"? "registro"?
Raymond Chen
2
using P = [](double)->void;é, AFAIK, C ++ 11 inválido. No entanto, isso é: using P = auto(double)->void;e produz um tipo de função (como P*um ponteiro de função).
dyp
2
O nome dele é Bjarne Stroustrup;) (observe o segundo r em Stroustrup) #
264 dec d
1
@RaymondChen: na verdade register, não soaria tão ruim, está em:register X as Y
MFH
1
Infelizmente, registerinicia uma declaração de variável, portanto isso já tem um significado. Declare uma variável de registro chamada Y do tipo X.
Raymond Chen