¿La diferencia de dos instancias constexpr de punteros __func__ sigue siendo constexpr?

14

¿Es esto válido C ++?

int main() {
    constexpr auto sz = __func__ - __func__;
    return sz;
}

GCC y MSVC piensan que está bien, Clang cree que no: Compiler Explorer .


Todos los compiladores están de acuerdo en que este está bien: Compiler Explorer .

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = p;
    constexpr auto sz = p2 - p;
    return sz;
}

A Clang nuevamente no le gusta este, pero los otros están de acuerdo: Compiler Explorer

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = __func__;
    constexpr auto sz = p2 - p;
    return sz;
}

¿Qué hay aquí arriba? Creo que la aritmética en punteros no relacionados es un comportamiento indefinido pero __func__devuelve el mismo puntero, ¿no? No estoy seguro, así que pensé que podría probarlo. Si recuerdo correctamente, std::equal_topuedo comparar punteros no relacionados sin un comportamiento indefinido:

#include <functional>

int main() {
    constexpr std::equal_to<const char*> eq{};
    static_assert(eq(__func__, __func__));
}

Clang piensa eq(__func__, __func__)que no es una expresión constante, aunque std::equal_to::operator() es constexpr . Otros compiladores no se quejan: Compiler Explorer


Clang tampoco compilará este. Se queja de que __func__ == __func__no es una expresión constante: Compiler Explorer

int main() {
    static_assert(__func__ == __func__);
}
Ayxan
fuente
De Function_definition , __func__es como si static const char __func__[] = "function-name";y ese equivalente es aceptado Demo ...
Jarod42
Curiosamente, funciona si inicializa una variable constexpr con __func__y la usa en static_assert ...
florestan
@ Jarod42 ¿Entonces esto es un error en Clang?
Ayxan
@florestan te gusta esto ? Tampoco se compilará con Clang. Mis ejemplos segundo y tercero en la pregunta son la forma en que mencionó. Uno compila, el otro no.
Ayxan
1
Consulte también CWG1962 , que podría eliminarse por __func__completo de la evaluación constexpr.
Davis Herring

Respuestas:

13

__func__en C ++ es un identificador. En particular, hace referencia a un objeto específico. De [dcl.fct.def.general] / 8 :

La variable predefinida local de función _­_­func_­_­se define como si fuera una definición del formulario

static const char __func__[] = "function-name";

había sido proporcionado, donde function-name es una cadena definida por la implementación. No se especifica si dicha variable tiene una dirección distinta de la de cualquier otro objeto en el programa.

Como una variable predefinida de función local , esta definición (como si) apareciera al comienzo del bloque de funciones. Como tal, cualquier uso __func__dentro de ese bloque se referirá a esa variable.

En cuanto a la parte "cualquier otro objeto", una variable define un objeto. __func__nombra el objeto definido por esa variable. Por lo tanto, dentro de una función, todos los usos del __func__nombre de la misma variable. Lo que no está definido es si esa variable es un objeto distinto de otros objetos.

Es decir, si está en una función llamada fooy usó el literal "foo"en otro lugar del problema, no está prohibido que una implementación tenga la variable __func__también el mismo objeto que el literal "foo"devuelve. Es decir, el estándar no requiere que cada función en la que __func__aparece debe almacenar datos separados del literal de cadena en sí.

Ahora, la regla "como si" de C ++ permite que las implementaciones se desvíen de esto, pero no pueden hacerlo de una manera que sea detectable. Entonces, mientras que la variable en sí misma puede o no tener una dirección distinta de otros objetos, los usos de __func__en la misma función deben comportarse como si se estuvieran refiriendo al mismo objeto.

Clang no parece implementarse de __func__esta manera. Parece implementarlo como si devolviera un literal de cadena prvalue del nombre de la función. Dos literales de cadena distintos no tienen que referirse al mismo objeto, por lo que restarles punteros es UB. Y el comportamiento indefinido en un contexto de expresión constante está mal formado.

Lo único que me hace dudar al decir que Clang está 100% equivocado aquí es [temp.arg.nontype] / 2 :

Para un parámetro de plantilla sin tipo de referencia o tipo de puntero, el valor de la expresión constante no se referirá (o para un tipo de puntero, no será la dirección de):

...

  • Una _­_­func_­_variable predefinida .

Mira, esto parece permitir algo de fraude por la implementación. Es decir, aunque __func__técnicamente puede ser una expresión constante, no puede usarse en un parámetro de plantilla. Se trata como un literal de cadena, aunque técnicamente es una variable.

Entonces, en cierto nivel, diría que el estándar está hablando desde ambos lados de su boca.

Nicol Bolas
fuente
Entonces, estrictamente hablando __func__puede ser una expresión constante en todos los casos en mi pregunta, ¿verdad? Entonces el código debería haberse compilado.
Ayxan
¿Qué pasa con el "No se especifica si dicha variable tiene una dirección distinta de la de cualquier otro objeto en el programa". ¿parte? Comportamiento no especificado significa no determinismo en el comportamiento de la máquina abstracta. ¿Puede ser problemático para la evaluación constexpr? ¿Qué pasa si la primera ocurrencia de __func__la dirección es la misma que la de otro objeto, y en la segunda __func__no es así? De acuerdo, eso no significa que la dirección difiera entre las dos instancias, ¡pero todavía estoy confundido!
Johannes Schaub - litb
@ JohannesSchaub-litb: " ¿Qué pasa con la parte" No se especifica si dicha variable tiene una dirección distinta de la de cualquier otro objeto en el programa "? " ¿Qué pasa con eso? __func__no es una macro; Es un identificador que nombra una variable específica y, por lo tanto, un objeto específico. Por lo tanto, cualquier uso de __func__en la misma función debería dar como resultado un valor gl que se refiera al mismo objeto. O más al punto, no se puede implementar de tal manera que este no sea el caso.
Nicol Bolas
@Nicol que se refiere al mismo objeto. Pero ese objeto puede en un instante tener la misma dirección que otro objeto. Y en el otro instante no. No digo que sea un problema, pero solo les recuerdo a todos esta posibilidad. Y luego, después de todo, también puedo estar equivocado, así que también digo esto con la esperanza de ser corregido o confirmado.
Johannes Schaub - litb
@ JohannesSchaub-litb: " Pero ese objeto puede tener en un instante la misma dirección que otro objeto " . Eso no está permitido en el modelo de objetos C ++. Dos objetos, ninguno de los cuales está anidado dentro del otro, no pueden estar dentro de sus vidas en el mismo almacenamiento al mismo tiempo. Y el objeto en cuestión tiene una duración de almacenamiento estático, por lo que, a menos que use la colocación new, no irá a ningún lado hasta que finalice el programa.
Nicol Bolas