La différence de deux instances constexpr de pointeurs __func__ est-elle toujours constexpr?

14

Est-ce un C ++ valide?

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

GCC et MSVC pensent que c'est OK, Clang pense que ce n'est pas le cas: Compiler Explorer .


Tous les compilateurs conviennent que celui-ci est OK: Explorateur de compilateurs .

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

Clang n'aime pas encore celui-ci, mais les autres sont d'accord avec lui: Compiler Explorer

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

Qu'est-ce qu'il y a ici? Je pense que l'arithmétique sur les pointeurs non liés est un comportement indéfini mais __func__renvoie le même pointeur, non? Je ne suis pas sûr, alors j'ai pensé que je pourrais le tester. Si je me souviens bien, std::equal_topeut comparer des pointeurs indépendants sans comportement indéfini:

#include <functional>

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

Clang pense que ce eq(__func__, __func__)n'est pas une expression constante, même si std::equal_to::operator() c'est constexpr . Les autres compilateurs ne se plaignent pas: Explorateur de compilateurs


Clang ne compilera pas celui-ci non plus. Se plaint que ce __func__ == __func__n'est pas une expression constante: Explorateur du compilateur

int main() {
    static_assert(__func__ == __func__);
}
Ayxan
la source
De Function_definition , __func__est comme si static const char __func__[] = "function-name";et cet équivalent est accepté Démo ...
Jarod42
Fait intéressant, cela fonctionne si vous initialisez une variable constexpr avec __func__et l'utilisez dans le static_assert ...
florestan
@ Jarod42 C'est donc un bug dans Clang?
Ayxan
@florestan comme ça ? Il ne compilera pas non plus avec Clang. Mes 2e et 3e exemples dans la question sont ceux que vous avez mentionnés. L'un compile, l'autre non.
Ayxan
1
Voir aussi CWG1962 , qui pourrait être __func__entièrement supprimé de l'évaluation constexpr.
Davis Herring

Réponses:

13

__func__en C ++ est un identifiant. En particulier, il fait référence à un objet spécifique. Depuis [dcl.fct.def.general] / 8 :

La variable prédéfinie locale de fonction _­_­func_­_­est définie comme si une définition du formulaire

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

avait été fourni, où nom-fonction est une chaîne définie par l'implémentation. Il n'est pas précisé si une telle variable a une adresse distincte de celle de tout autre objet du programme.

En tant que variable prédéfinie locale de fonction , cette définition (comme si) apparaît au début du bloc fonction. En tant que tel, toute utilisation de l' __func__intérieur de ce bloc fera référence à cette variable.

Quant à la partie "tout autre objet", une variable définit un objet. __func__nomme l'objet défini par cette variable. Par conséquent, dans une fonction, toutes les utilisations du __func__nom de la même variable. Ce qui n'est pas défini, c'est si cette variable est un objet distinct des autres objets.

Autrement dit, si vous êtes dans une fonction nommée fooet que vous avez utilisé le littéral "foo"ailleurs dans le problème, il n'est pas interdit à une implémentation que la variable __func__soit également le même objet que le littéral "foo"renvoie. Autrement dit, la norme n'exige pas que chaque fonction dans laquelle __func__apparaît s'affiche stocke des données distinctes du littéral de chaîne lui-même.

Maintenant, la règle "comme si" de C ++ permet aux implémentations de s'en écarter, mais elles ne peuvent pas le faire d'une manière qui serait détectable. Ainsi, alors que la variable elle-même peut ou non avoir une adresse distincte des autres objets, les utilisations de __func__dans la même fonction doivent se comporter comme si elles faisaient référence au même objet.

Clang ne semble pas implémenter de __func__cette façon. Il semble l'implémenter comme s'il renvoyait un littéral de chaîne de valeur du nom de la fonction. Deux littéraux de chaîne distincts n'ont pas à faire référence au même objet, donc la soustraction de pointeurs vers eux est UB. Et un comportement indéfini dans un contexte d'expression constante est mal formé.

La seule chose qui me fait hésiter à dire que Clang a 100% tort ici est [temp.arg.nontype] / 2 :

Pour un paramètre-modèle non type de référence ou de type pointeur, la valeur de l'expression constante ne doit pas faire référence (ou pour un type pointeur, ne doit pas être l'adresse de):

...

  • une _­_­func_­_variable prédéfinie .

Voir, cela semble permettre une certaine fudging par la mise en œuvre. Autrement dit, bien qu'il __func__puisse être techniquement une expression constante, vous ne pouvez pas l'utiliser dans un paramètre de modèle. Il est traité comme un littéral de chaîne, même s'il s'agit techniquement d'une variable.

Donc, à un certain niveau, je dirais que la norme parle des deux côtés de sa bouche.

Nicol Bolas
la source
Donc, à proprement parler __func__peut être une expression constante dans tous les cas de ma question, non? Le code aurait donc dû être compilé.
Ayxan
Qu'en est-il du "Il n'est pas spécifié si une telle variable a une adresse distincte de celle de tout autre objet dans le programme." partie? Un comportement non spécifié signifie non-déterminisme dans le comportement de la machine abstraite. Cela peut-il être problématique pour l'évaluation constexpr? Que faire si pour la première occurrence de __func__l'adresse est la même que celle d'un autre objet, et dans la deuxième occurrence de __func__celui-ci ne l'est pas? Certes, cela ne signifie pas que l'adresse diffère entre les deux instances, mais je suis toujours confus!
Johannes Schaub - litb
@ JohannesSchaub-litb: " Qu'en est-il de la" Il est indéterminé si une telle variable a une adresse distincte de celle de tout autre objet dans le programme. "Partie? " Qu'en est-il? __func__n'est pas une macro; c'est un identifiant qui nomme une variable spécifique et donc un objet spécifique. Par conséquent, toute utilisation de __func__dans la même fonction doit entraîner une valeur gl qui fait référence au même objet. Ou plus précisément, il ne peut pas être mis en œuvre de telle manière que ce ne serait pas le cas.
Nicol Bolas
@Nicol qui fait référence au même objet. Mais cet objet peut à un instant avoir la même adresse qu'un autre objet. Et à l'autre instant, non. Je ne dis pas que c'est un problème, mais je rappelle simplement à tout le monde cette possibilité. Et puis, après tout, je peux aussi me tromper, alors je dis aussi cela dans l'espoir d'être corrigé ou d'être confirmé.
Johannes Schaub - litb
@ JohannesSchaub-litb: " Mais cet objet peut à un instant avoir la même adresse qu'un autre objet. " Ce n'est pas autorisé sous le modèle d'objet C ++. Deux objets, ni l'un ni l'autre étant imbriqués l'un dans l'autre, ne peuvent pas tous les deux faire partie de leur durée de vie dans le même stockage en même temps. Et l'objet en question a une durée de stockage statique, donc à moins que vous n'utilisiez placement - newdessus, il ne va nulle part jusqu'à la fin du programme.
Nicol Bolas