¿Es cada uso "normal" de literales definidos por el usuario comportamiento indefinido?

8

Los literales definidos por el usuario deben comenzar con un guión bajo.

Esta es una regla más o menos universalmente conocida que se puede encontrar en todos los sitios redactados hablando de literales de usuario. También es una regla que yo (¿y posiblemente otros?) He ignorado descaradamente desde entonces sobre una base de "qué mierda". Ahora, por supuesto, eso no es correcto. En el sentido más estricto, esto utiliza un identificador reservado y, por lo tanto, invoca Comportamiento indefinido (aunque prácticamente no obtiene un encogimiento de hombros del compilador).

Entonces, reflexionando si debería continuar ignorando deliberadamente esa parte (en mi opinión inútil) de la norma o no, decidí mirar lo que realmente está escrito. Porque, ya sabes, qué importa lo que todos sepan . Lo que importa es lo que está escrito en el estándar.

[over.literal]establece que "algunos" identificadores de sufijo literales están reservados, vinculando a [usrlit.suffix]. El último establece que todos están reservados, excepto aquellos que comienzan con un guión bajo. OK, eso es exactamente lo que ya sabíamos, escrito explícitamente (o más bien, escrito al revés).

Además, [over.literal]contiene una Nota que sugiere algo obvio pero preocupante:

a excepción de las restricciones descritas anteriormente, son funciones comunes de espacio de nombres y plantillas de funciones

Bueno, claro que lo son. En ninguna parte dice que no lo son, entonces, ¿qué más esperarías que sean?

Pero espera un momento. [lex.name]establece explícitamente que cada identificador que comienza con un guión bajo en el espacio de nombres global está reservado.

Ahora, un operador literal generalmente, a menos que lo coloque explícitamente en un espacio de nombres (¡lo cual, creo que nadie hace !?), está muy en el espacio de nombres global. Entonces, el nombre, que debe comenzar con un guión bajo, está reservado. No se menciona una excepción especial. Entonces, cada nombre (con guión bajo o sin él) es un nombre reservado.

¿Se espera que ponga literales definidos por el usuario en un espacio de nombres porque el uso "normal" (subrayado o no) está usando un nombre reservado?

Damon
fuente
1
Me pregunto si los sufijos UDL cuentan como identificadores.
HolyBlackCat
1
FWIW su código debe estar en un espacio de nombres y si lo sigue, está a salvo.
NathanOliver
@ NathanOliver-ReinstateMonica: ¿Cómo podría usar ese literal entonces? Digamos que lo puse, lo que sea, digamos ... _km(por kilómetros) en el espacio de nombres udl. ¿Entonces un literal para 5 km parece ... 5udl::_km?
Damon
@ NathanOliver-ReinstateMonica Eso fue lo que pensé ... pero eso no es cierto, mira mi respuesta.
Konrad Rudolph el
1
@Damon Para eso usingestán las declaraciones. En el ámbito donde necesita usar el literal, tenga una instrucción de uso que lo importe.
NathanOliver

Respuestas:

6

Sí: la combinación de prohibir el uso _como el inicio de un identificador global junto con la exigencia de UDL no estándar para comenzar _significa que no puede colocarlos en el espacio de nombres global. Pero no debe ensuciar el espacio de nombres global con cosas, especialmente UDL, por lo que no debería ser un gran problema.

El idioma tradicional, como lo usa el estándar, es colocar UDL en un literalsespacio de nombres (y si tiene diferentes conjuntos de UDL, entonces los coloca en diferentes inline namespacesdebajo de ese espacio de nombres). Ese literalsespacio de nombres suele estar debajo del principal. Cuando desee utilizar un conjunto particular de UDL, invoque using namespace my_namespace::literalso el subespacio de nombres que contenga su conjunto literal de elección.

Esto es importante porque las UDL tienden a estar muy abreviadas. El estándar, por ejemplo, utiliza spara std::string, pero también para std::chrono::durationde segundos. Si bien se aplican a diferentes tipos de literales ( saplicado a una cadena es una cadena, mientras que saplicado a un número es una duración), a veces puede ser confuso leer código que usa literales abreviados. Por lo tanto, no debe lanzar literales a todos los usuarios de su biblioteca; deberían optar por usarlos.

Mediante el uso de diferentes espacios de nombres para estos ( std::literals::string_literalsy std::literals::chrono_literals), el usuario puede ser franco sobre qué conjuntos de literales desean en qué partes del código.

Nicol Bolas
fuente
1
Lamentablemente, esta respuesta no parece abordar la validez de los _Foosufijos, que se omitieron de la pregunta pero son bastante problemáticos.
Konrad Rudolph el
3

Esta es una buena pregunta, y no estoy seguro acerca de la respuesta, pero creo que la respuesta es "no, no es UB" basado en una lectura particular del estándar.

[lex.name] /3.2 lee:

Cada identificador que comienza con un guión bajo está reservado a la implementación para su uso como nombre en el espacio de nombres global.

Ahora, claramente, la restricción "como un nombre en el espacio de nombres global" debe leerse como que se aplica a toda la regla, no solo a cómo la implementación puede usar el nombre. Es decir, su significado no es

"cada identificador que comienza con un guión bajo está reservado para la implementación, Y la implementación puede usar identificadores como nombres en el espacio de nombres global"

sino más bien

"el uso de cualquier identificador que comience con un guión bajo como nombre en el espacio de nombres global está reservado para la implementación".

(Si creyéramos la primera interpretación, significaría que nadie podría declarar una función llamada my_namespace::_foo, por ejemplo).

Según la segunda interpretación, algo así como una declaración global de operator""_foo(en el ámbito global) es legal, porque dicha declaración no se usa _foocomo un nombre. Más bien, el identificador es solo una parte del nombre real, que es operator""_foo(que no comienza con un guión bajo).

Brian
fuente
Iba a ir con una interpretación similar. Teniendo en cuenta que uno puede definir una función como void operator+(foo, bar), donde claramente el nombre de la función no es un identificador, sino un nombre. Lo mismo vale para operator "" _fooser el nombre en nuestro caso.
StoryTeller - Unslander Monica
2

¿Es cada uso "normal" de literales definidos por el usuario comportamiento indefinido?

Claramente no.

El siguiente es el uso idiomático (y por lo tanto definitivamente "normal") de UDL, y está bien definido de acuerdo con la regla que acaba de enumerar:

namespace si {
    struct metre {  };

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

Ha enumerado casos problemáticos y estoy de acuerdo con su evaluación sobre su validez, pero se pueden evitar fácilmente en un código idiomático de C ++, por lo que no veo completamente el problema con la redacción actual, incluso si fue potencialmente accidental.

Según el ejemplo en [over.literal] / 8, incluso podemos usar letras mayúsculas después del guión bajo:

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)

Por lo tanto, lo único problemático parece ser el hecho de que el estándar hace que el espacio en blanco entre ""el nombre UDL sea significativo.

Konrad Rudolph
fuente
Buena captura, ni siquiera pensé en las unidades SI mayúsculas.
Damon
1
El estándar contiene un ejemplo que muestra que operator""_Bqestá bien (lo que significa que operator""_Ktambién está bien). El truco es omitir el espacio entre ""y el sufijo. Ver C ++ 17 [over.literal] / 8.
Brian
@Brian Odio cuando los ejemplos son la única fuente de redacción normativa. Gran descubrimiento. Y el hecho de que decidieron hacer que este espacio en blanco sea significativo es aún más complicado. De cualquier manera, he arreglado mi respuesta.
Konrad Rudolph el
No es que el ejemplo sea la única fuente. Más bien, esto se deduce del hecho de que un literal de cadena definido por el usuario es un token de preprocesamiento único. Dicho esto, el ejemplo aclara la intención, además, nunca me habría dado cuenta de esto si no fuera por el ejemplo.
Brian
1

Sí, definir su propio literal definido por el usuario en el espacio de nombres global da como resultado un programa mal formado.

No me he encontrado con esto yo mismo, porque trato de seguir la regla:

No coloque nada (además de mainespacios de nombres y extern "C"cosas para la estabilidad ABI) en el espacio de nombres 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";
}

Esto también significa que no puede usarlo _CAPScomo su nombre literal, incluso en un espacio de nombres.

Espacios de nombres en línea llamados literals son una excelente manera de empaquetar los operadores literales definidos por el usuario. Se pueden importar donde desee usarlo sin tener que nombrar exactamente qué literales desea, o si importa todo el espacio de nombres también obtendrá los literales.

Esto sigue cómo la stdbiblioteca maneja los literales también, por lo que debería ser familiar para los usuarios de su código.

Yakk - Adam Nevraumont
fuente
0

Dado el literal con sufijo _X, la gramática llama a _Xun "identificador" .

Entonces, sí: el estándar, presumiblemente sin darse cuenta, ha hecho imposible crear un UDT de alcance global, o UDT que comiencen con una letra mayúscula, en un programa bien definido. (Tenga en cuenta que lo primero no es algo que generalmente quiera hacer de todos modos)

Esto no se puede resolver editorialmente: los nombres de los literales definidos por el usuario tendrían que tener su propio "espacio de nombres" léxico que evite conflictos con (por ejemplo) nombres de funciones proporcionadas por la implementación. En mi opinión, sin embargo, hubiera sido bueno que hubiera una nota no normativa en alguna parte, señalando las consecuencias de estas reglas y señalando que son deliberadas.

Carreras de ligereza en órbita
fuente
Incluso con una excepción, ¿una implementación definida int _x(int);(en global) no causará un error de compilación?
Richard Critten
Lectura adicional potencialmente interesante: CWG 1882
Carreras de ligereza en órbita el
1
@ RichardCritten ¿Qué quieres decir?
Carreras de ligereza en órbita el
Entendí: "... posiblemente debería tener una exención ..." para indicar que los operadores definidos por el usuario del formulario _xdeberían estar exentos [lex.name]. Si no he entendido bien, lo que sigue es basura . Si la implementación ya había declarado una función int _x(int);en el ámbito global (nombre reservado así que bien), entonces un usuario declarado long double operator "" _x(long double);(por ejemplo) obtendría un error de compilación.
Richard Critten
No veo cómo una exención puede curar este problema.
Richard Critten