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?
fonte
_km
(por quilômetros) no espaço para nomeudl
. Então um literal para 5 km parece ...5udl::_km
?using
servem as declarações. No escopo em que você precisa usar o literal, tenha uma instrução using que o importe.Respostas:
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
literals
espaço para nome (e se você tiver conjuntos diferentes de UDLs, coloque-os em diferentesinline namespaces
abaixo desse espaço para nome). Esseliterals
espaço para nome geralmente fica abaixo do principal. Quando você deseja usar um conjunto específico de UDLs, invocausing namespace my_namespace::literals
ou 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
s
porstd::string
, mas também porstd::chrono::duration
segundos. Embora eles se apliquem a diferentes tipos de literais (s
aplicado a uma string é uma string, enquantos
aplicado 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_literals
estd::literals::chrono_literals
), o usuário pode ser franco quanto a quais conjuntos de literais deseja em quais partes do código.fonte
_Foo
sufixos, que foram omitidos da pergunta, mas são bastante problemáticos.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ê:
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_foo
como nome. Em vez disso, o identificador é apenas uma parte do nome real, que éoperator""_foo
(que não começa com um sublinhado).fonte
void operator+(foo, bar)
, onde claramente o nome da função não é um identificador, mas é um nome. O mesmo vale paraoperator "" _foo
ser o nome no nosso caso.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:
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:
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.fonte
operator""_Bq
está ok (o que significa queoperator""_K
também está ok). O truque é omitir o espaço entre""
e o sufixo. Veja C ++ 17 [over.literal] / 8.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
main
espaços para nome e outrosextern "C"
itens para a estabilidade da ABI) no espaço para nome global.Isso também significa que você não pode usar
_CAPS
como seu nome literal, mesmo em um espaço para nome.Os namespaces embutidos chamados
literals
sã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
std
biblioteca lida com literais também, portanto, isso deve ser familiar aos usuários do seu código.fonte
Dado o literal com sufixo
_X
, a gramática chama_X
um "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.
fonte
int _x(int);
(em global) não causará um erro de compilação?_x
devem ser isentos[lex.name]
. Se eu entendi errado, o que se segue é lixo . Se a implementação já tiver declarado uma funçãoint _x(int);
no escopo global (nome reservado, então ok), um usuário declaradolong double operator "" _x(long double);
(por exemplo) receberá um erro de compilação.