Todo uso “normal” de literais definidos pelo usuário é um comportamento indefinido?


8

Literais definidos pelo usuário devem começar com um sublinhado.

Essa é uma regra mais ou menos universalmente conhecida que você pode encontrar em todos os sites leigos que falam sobre literais de usuários. É também uma regra que eu (e possivelmente outros?) Tenho ignorado descaradamente desde então em uma base "que besteira". Agora, é claro, isso estritamente não está correto. No sentido mais estrito, isso usa um identificador reservado e, assim, invoca o Comportamento indefinido (embora você não receba quase nada do compilador, praticamente).

Então, pensando se devo continuar a ignorar deliberadamente que (na minha opinião é inútil) parte do padrão ou não, decidi examinar o que realmente está escrito. Porque, você sabe, o que importa o que todo mundo sabe . O que importa é o que está escrito no padrão.

[over.literal]afirma que "alguns" identificadores de sufixo literais estão reservados, vinculados a [usrlit.suffix]. O último afirma que todos são reservados, exceto aqueles que começam com um sublinhado. OK, é exatamente o que já sabíamos, escrito explicitamente (ou melhor, escrito de trás para frente).

Além disso, [over.literal]contém uma Nota que sugere uma coisa óbvia, mas preocupante:

exceto pelas restrições descritas acima, são funções comuns de escopo de espaço para nome e modelos de função

Bem, claro que são. Em nenhum lugar diz que eles não são, então o que mais você espera que eles sejam.

Mas espere um momento. [lex.name]afirma explicitamente que cada identificador que começa com um sublinhado no espaço para nome global é reservado.

Agora, normalmente, um operador literal, a menos que você o coloque explicitamente em um espaço para nome (o qual, acredito que ninguém o faça !?), está muito no espaço para nome global. Portanto, o nome, que deve começar com um sublinhado, é reservado. Não há menção de uma exceção especial. Portanto, todo nome (com sublinhado ou sem) é um nome reservado.

Você realmente deve colocar literais definidos pelo usuário em um espaço para nome porque o uso "normal" (sublinhado ou não) está usando um nome reservado?


1
Gostaria de saber se os sufixos UDL contam como identificadores.
precisa saber é o seguinte

1
FWIW, seu código deve estar em um espaço para nome e, se você seguir, estará seguro.
NathanOliver 4/04/19

@ NathanOliver-ReinstateMonica: Como eu usaria esse literal então? Digamos que eu coloquei, seja o que for, diga ... _km(por quilômetros) no espaço para nome udl. Então um literal para 5 km parece ... 5udl::_km?
Damon

@ NathanOliver-ReinstateMonica Isso é o que eu pensei ... mas isso não é verdade, veja minha resposta.
Konrad Rudolph

1
@ Damon É para isso que usingservem as declarações. No escopo em que você precisa usar o literal, tenha uma instrução using que o importe.
NathanOliver 4/04/19

Respostas:


6

Sim: a combinação de proibir o uso _como o início de um identificador global, além de exigir que UDLs não padrão iniciem _significa que você não pode colocá-los no espaço para nome global. Mas você não deve sujar o espaço de nomes global com outras coisas, especialmente UDLs, para que isso não seja um problema.

O idioma tradicional, conforme usado pelo padrão, é colocar UDLs em um literalsespaço para nome (e se você tiver conjuntos diferentes de UDLs, coloque-os em diferentes inline namespacesabaixo desse espaço para nome). Esse literalsespaço para nome geralmente fica abaixo do principal. Quando você deseja usar um conjunto específico de UDLs, invoca using namespace my_namespace::literalsou qualquer subdomínio que contenha seu conjunto literal de opções.

Isso é importante porque as UDLs tendem a ser muito abreviadas. O padrão, por exemplo, usa spor std::string, mas também por std::chrono::durationsegundos. Embora eles se apliquem a diferentes tipos de literais ( saplicado a uma string é uma string, enquanto saplicado a um número é uma duração), às vezes pode ser confuso ler código que usa literais abreviados. Portanto, você não deve fornecer literais a todos os usuários da sua biblioteca; eles devem optar por usá-los.

Ao usar espaços de nomes diferentes para esses ( std::literals::string_literalse std::literals::chrono_literals), o usuário pode ser franco quanto a quais conjuntos de literais deseja em quais partes do código.


1
Infelizmente, essa resposta não parece abordar a validade dos _Foosufixos, que foram omitidos da pergunta, mas são bastante problemáticos.
Konrad Rudolph

3

Essa é uma boa pergunta e não tenho certeza da resposta, mas acho que a resposta é "não, não é UB", com base em uma leitura específica do padrão.

[lex.name] /3.2 lê:

Cada identificador que começa com um sublinhado é reservado à implementação para uso como um nome no espaço para nome global.

Agora, claramente, a restrição "como um nome no espaço de nomes global" deve ser lida como aplicável a toda a regra, não apenas à maneira como a implementação pode usar o nome. Ou seja, seu significado não é

"cada identificador que começa com um sublinhado é reservado à implementação, E a implementação pode usar identificadores como nomes no espaço para nome global"

mas sim,

"o uso de qualquer identificador que comece com um sublinhado como um nome no espaço para nome global é reservado à implementação".

(Se acreditássemos na primeira interpretação, isso significaria que ninguém poderia declarar uma função chamada my_namespace::_foo, por exemplo.)

Sob a segunda interpretação, algo como uma declaração global de operator""_foo(no escopo global) é legal, porque essa declaração não usa _foocomo nome. Em vez disso, o identificador é apenas uma parte do nome real, que é operator""_foo(que não começa com um sublinhado).


Eu estava indo com uma interpretação semelhante. Considerando que se pode definir uma função como void operator+(foo, bar), onde claramente o nome da função não é um identificador, mas é um nome. O mesmo vale para operator "" _fooser o nome no nosso caso.
StoryTeller - Unslander Monica 04/12/19

2

Todo uso “normal” de literais definidos pelo usuário é um comportamento indefinido?

Claramente não.

A seguir, é apresentado o uso idiomático (e, portanto, definitivamente "normal") de UDLs, e está bem definido de acordo com a regra que você acabou de listar:

namespace si {
    struct metre {  };

    constexpr metre operator ""_m(long double value) { return metre{value}; }
}

Você listou casos problemáticos e eu concordo com sua avaliação sobre a validade deles, mas eles são facilmente evitados no código C ++ idiomático, para que eu não veja completamente o problema com o texto atual, mesmo que potencialmente acidental.

De acordo com o exemplo em [over.literal] / 8, podemos até usar letras maiúsculas após o sublinhado:

float operator ""E(const char*);    // error: reserved literal suffix (20.5.4.3.5, 5.13.8)
double operator""_Bq(long double);  // OK: does not use the reserved identifier _Bq (5.10)
double operator"" _Bq(long double); // uses the reserved identifier _Bq (5.10)

A única coisa problemática, portanto, parece ser o fato de o padrão tornar o espaço em branco entre ""e o nome da UDL significativo.


Boa captura, eu nem pensei em unidades SI com letras maiúsculas.
Damon

1
O padrão contém um exemplo que mostra que operator""_Bqestá ok (o que significa que operator""_Ktambém está ok). O truque é omitir o espaço entre ""e o sufixo. Veja C ++ 17 [over.literal] / 8.
Brian

@ Brian Eu odeio quando exemplos são a única fonte de formulação normativa. Ótima descoberta. E o fato de eles terem decidido tornar esse espaço em branco significativo é ainda mais confuso. De qualquer maneira, eu corrigi minha resposta.
Konrad Rudolph

Não é que o exemplo seja a única fonte. Em vez disso, isso decorre do fato de que uma literal de cadeia definida pelo usuário é um único token de pré-processamento. Dito isto, o exemplo esclarece a intenção, e eu nunca teria percebido isso se não fosse o exemplo.
Brian

1

Sim, definir seu próprio literal definido pelo usuário no namespace global resulta em um programa mal formado.

Eu não encontrei isso sozinho, porque tento seguir a regra:

Não coloque nada (além de mainespaços para nome e outros extern "C"itens para a estabilidade da ABI) no espaço para nome global.

namespace Mine {
  struct meter { double value; };
  inline namespace literals {
    meter operator ""_m( double v ) { return {v}; }
  }
}

int main() {
  using namespace Mine::literals;
  std::cout << 15_m.value << "\n";
}

Isso também significa que você não pode usar _CAPScomo seu nome literal, mesmo em um espaço para nome.

Os namespaces embutidos chamados literalssão uma ótima maneira de empacotar seus operadores literais definidos pelo usuário. Eles podem ser importados para o local em que você deseja usá-lo sem precisar nomear exatamente quais literais você deseja ou, se você importar todo o espaço para nome, também obtém os literais.

A seguir, como a stdbiblioteca lida com literais também, portanto, isso deve ser familiar aos usuários do seu código.


0

Dado o literal com sufixo _X, a gramática chama _Xum "identificador" .

Portanto, sim: presumivelmente, o padrão tornou impossível criar uma UDT no escopo global, ou UDTs que começam com letra maiúscula, em um programa bem definido. (Observe que o primeiro não é algo que você geralmente queira fazer!)

Isso não pode ser resolvido editorialmente: os nomes dos literais definidos pelo usuário teriam que ter seu próprio "espaço para nome" lexical que impedisse conflitos com (por exemplo) nomes de funções fornecidas pela implementação. Na minha opinião, seria bom que houvesse uma nota não normativa em algum lugar, apontando as consequências dessas regras e apontando que elas são deliberadas.


Mesmo com uma exceção, uma implementação definida int _x(int);(em global) não causará um erro de compilação?
Richard Critten 04/12/19

Leitura adicional potencialmente interessante: CWG 1882
Lightness Races in Orbit

1
@RichardCritten O que você quer dizer?
Lightness Races in Orbit

Eu entendi: "... sem dúvida deve ter uma isenção ..." para indicar que operadores do formulário definidos pelo usuário _xdevem ser isentos [lex.name]. Se eu entendi errado, o que se segue é lixo . Se a implementação já tiver declarado uma função int _x(int);no escopo global (nome reservado, então ok), um usuário declarado long double operator "" _x(long double);(por exemplo) receberá um erro de compilação.
Richard Critten 04/12/19

Não vejo como uma isenção pode curar esse problema.
Richard Critten 04/12/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.