Nullptr peut-il être converti en uintptr_t? Différents compilateurs en désaccord

10

Considérez ce programme:

#include <cstdint>
using my_time_t = uintptr_t;

int main() {
    const my_time_t t = my_time_t(nullptr);
}

Il n'a pas réussi à compiler avec msvc v19.24:

<source>(5): error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'my_time_t'
<source>(5): note: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
<source>(5): error C2789: 't': an object of const-qualified type must be initialized
<source>(5): note: see declaration of 't'

Compiler returned: 2

mais clang (9.0.1) et gcc (9.2.1) "mangent" ce code sans aucune erreur.

J'aime le comportement MSVC, mais est-il confirmé par la norme? En d'autres termes, est-ce un bug dans clang / gcc ou il est possible d'interpréter la norme que c'est un bon comportement de gcc / clang?

user1244932
la source
2
J'ai lu cela comme une initialisation de copie à partir d'une distribution de style fonction. Cela est ensuite interprété par le compilateur comme l'un des transtypages C ++ "même s'il ne peut pas être compilé". Peut-être qu'il y a une incohérence entre les compilateurs quant à la façon dont le casting est interprété
wreckgar23
Pour autant que je sache, MSVC v19.24 ne prend pas en charge un mode C ++ 11. Voulez-vous dire C ++ 14 ou C ++ 17 à la place?
noyer

Réponses:

5

À mon avis, MSVC ne se comporte pas conformément aux normes.

Je fonde cette réponse sur C ++ 17 (projet N4659), mais C ++ 14 et C ++ 11 ont un libellé équivalent.

my_time_t(nullptr)est une expression de suffixe et, comme il my_time_ts'agit d'un type et d' (nullptr)une expression unique dans une liste d'initialisation entre parenthèses, elle est exactement équivalente à une expression de transtypage explicite. ( [expr.type.conv] / 2 )

Le transtypage explicite essaie quelques conversions spécifiques C ++ différentes (avec extensions), en particulier également reinterpret_cast. ( [expr.cast] /4.4 ) Les transtypages essayés auparavant reinterpret_castsont const_castet static_cast(avec des extensions et également en combinaison), mais aucun d'entre eux ne peut être converti std::nullptr_ten un type intégral.

Mais reinterpret_cast<my_time_t>(nullptr)devrait réussir parce que [expr.reinterpret.cast] / 4 dit qu'une valeur de type std::nullptr_tpeut être convertie en un type intégral comme si par reinterpret_cast<my_time_t>((void*)0), ce qui est possible car my_time_t = std::uintptr_tdevrait être un type suffisamment grand pour représenter toutes les valeurs du pointeur et dans cette condition, le le même paragraphe standard permet la conversion d' void*un type intégral.

Il est particulièrement étrange que MSVC autorise la conversion si la notation cast plutôt que la notation fonctionnelle est utilisée:

const my_time_t t = (my_time_t)nullptr;
noyer
la source
1
Oui. Notez que, static_casten particulier, certains cas visent à piéger l'échelle en fonte de style C (par exemple, une fonte de style C vers une base ambiguë est mal formée static_castplutôt qu'un reinterpret_cast), mais aucun ne s'applique ici.
TC
my_time_t(nullptr)est par définition le même que (my_time_t)nullptr, donc MSVC a certainement tort d'accepter l'un et de rejeter l'autre.
Richard Smith
2

Bien que je ne puisse trouver aucune mention explicite dans ce Working Draft C ++ Standard (de 2014) que la conversion de std::nullptr_tvers un type intégral est interdite, il n'y a pas non plus mention qu'une telle conversion est autorisée!

Cependant, le cas de la conversion de std::nullptr_tvers bool est explicitement mentionné:

4.12 Conversions booléennes
Une valeur de type arithmétique, énumération non étendue, pointeur ou pointeur vers le type de membre peut être convertie en une valeur de type bool. Une valeur nulle, une valeur de pointeur nulle ou une valeur de pointeur de membre nul est convertie en faux; toute autre valeur est convertie en true. Pour l'initialisation directe (8.5), une valeur de type std :: nullptr_t peut être convertie en une valeur de type bool; la valeur résultante est fausse.

De plus, le seul endroit dans ce projet de document où la conversion de std::nullptr_tà un type intégral est mentionné, est dans la section "reinterpret_cast":

5.2.10 Reinterpret cast
...
(4) Un pointeur peut être explicitement converti en n'importe quel type entier suffisamment grand pour le contenir. La fonction de mappage est définie par l'implémentation. [Remarque: Il est destiné à ne pas surprendre ceux qui connaissent la structure d'adressage de la machine sous-jacente. - note de fin] Une valeur de type std :: nullptr_t peut être convertie en type intégral; la conversion a la même signification et la même validité qu'une conversion de (void *) 0 en type intégral. [Remarque: Un reinterpret_cast ne peut pas être utilisé pour convertir une valeur de n'importe quel type en type std :: nullptr_t. - note de fin]

Ainsi, à partir de ces deux observations, on pourrait (à mon humble avis) raisonnablement supposer que le MSVCcompilateur est correct.

EDIT : Cependant, votre utilisation de la «fonte de notation fonctionnelle» peut en fait suggérer le contraire! Le MSVCcompilateur n'a aucun problème à utiliser un cast de style C, par exemple:

uintptr_t answer = (uintptr_t)(nullptr);

mais (comme dans votre code), il s'en plaint:

uintptr_t answer = uintptr_t(nullptr); // error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'uintptr_t'

Pourtant, à partir du même projet de norme:

5.2.3 Conversion de type explicite (notation fonctionnelle)
(1) Un spécificateur de type simple (7.1.6.2) ou un spécificateur de nom de type (14.6) suivi d'une liste d'expressions entre parenthèses construit une valeur du type spécifié en fonction de la liste d'expressions. Si la liste d'expressions est une expression unique, l'expression de conversion de type est équivalente (dans sa définition et si elle est définie dans sa signification) à l'expression de transtypage correspondante (5.4). ...

L '"expression de conversion correspondante (5.4)" peut faire référence à une conversion de style C.

Adrian Mole
la source
0

Tous sont conformes à la norme (réf. Projet n4659 pour C ++).

nullptr est défini dans [lex.nullptr] comme:

Le littéral de pointeur est le mot clé nullptr. C'est une valeur de type std :: nullptr_t. [Remarque: ..., une valeur de ce type est une constante de pointeur nul et peut être convertie en valeur de pointeur nulle ou en valeur de pointeur de membre nul.]

Même si les notes ne sont pas normatives, celle-ci indique clairement que pour la norme, elle nullptrdevrait être convertie en une valeur de pointeur nulle .

On retrouve plus tard dans [conv.ptr]:

Une constante de pointeur nul est un littéral entier de valeur zéro ou une valeur de type std :: nullptr_t. Une constante de pointeur nul peut être convertie en un type de pointeur; .... Une constante de pointeur nul de type intégral peut être convertie en une valeur de type std :: nullptr_t.

Là encore, ce qui est requis par la norme, c'est qu'il 0peut être converti en a std::nullptr_tet qui nullptrpeut être converti en n'importe quel type de pointeur.

Ma lecture est que la norme n'a aucune exigence quant à savoir si elle nullptrpeut être convertie directement en type intégral ou non. À partir de là:

  • MSVC a une lecture stricte et interdit la conversion
  • Clang et gcc se comportent comme si une void *conversion intermédiaire était impliquée.
Serge Ballesta
la source
1
Je pense que cela est faux. Certaines constantes de pointeur nul sont des littéraux entiers avec une valeur zéro, mais ce nullptrn'est pas le cas car elles ont le type non intégral std::nullptr_t. 0 peut être converti en std::nullptr_tvaleur, mais pas en littéral nullptr. Tout cela est intentionnel, std::nullptr_test un type plus restreint pour empêcher les conversions involontaires.
MSalters
@MSalters: Je pense que vous avez raison. Je voulais le reformuler et je l'ai mal fait. J'ai édité mon article avec votre commentaire. Merci de votre aide.
Serge Ballesta