Est-ce que le casting de std :: pair const & à std :: pair const & safe?

Est-il prudent (en théorie ou en pratique) de reinterpret_cast un std::pair const & dans un std::pair const & , en supposant que le programmeur n’ait pas intentionnellement fait quelque chose de bizarre comme spécialisation std::pair ?

Ce n’est pas portable pour le faire.

std::pair exigences de std::pair sont définies dans la clause 20.3. La clause 17.5.2.3 précise que

Les articles 18 à 30 et l’Annexe D ne précisent pas la représentation des classes et omettent intentionnellement de spécifier des membres. Une implémentation peut définir des membres de classe statiques ou non statiques, ou les deux, selon les besoins pour implémenter la sémantique des fonctions de membre spécifiées dans les clauses 18 à 30 et dans l’annexe D.

Cela implique qu’il est légal (bien qu’incroyablement improbable) qu’une implémentation inclue une spécialisation partielle telle que:

 template struct pair { T1 first; T2 second; }; template struct pair { T2 second; const T1 first; }; 

qui ne sont clairement pas compatibles avec la mise en page. D’autres variantes, notamment l’inclusion de membres de données non statiques supplémentaires, éventuellement avant le first et / ou le second sont également autorisées dans le cadre de la règle.


Maintenant, il est assez intéressant d’examiner le cas où la disposition est connue. Bien que Potatoswatter ait souligné le DR 1334 qui affirme que T et const T ne sont pas compatibles avec la présentation , la norme fournit suffisamment de garanties pour nous permettre de tirer le meilleur parti de toutes les manières possibles:

 template struct mypair { T1 first; T2 second; }; mypair pair1; mypair* p1 = &pair1; int* p2 = reinterpret_cast(p1); // legal by 9.2p20 const int* p3 = p2; mypair* p4 = reinterpret_cast*>(p3); // again 9.2p20 

Cependant, cela ne fonctionne pas sur std::pair car nous ne pouvons pas appliquer 9.2p20 sans savoir que le first est en réalité le membre initial, ce qui n’est pas spécifié.

pair est définie à la section 20.3.2 de la norme pour avoir des membres de données:

 template  struct pair { T1 first; T2 second; }; 

Cela signifie que pour les types concrets T1 , T2 , la pair et la pair ont la garantie d’avoir des membres de données respectifs:

 struct pair { T1 first; T2 second; }; struct pair { const T1 first; T2 second; }; 

Maintenant, si T1 et T2 sont tous les deux une mise en page standard , alors la pair et la pair sont toutes les deux une mise en page standard. Comme indiqué ci-dessus, par DR1334, ils ne sont pas compatibles avec la structure (3.9p11), mais par 9.2p19, ils peuvent être reinterpret_cast sur leur premier membre respectif T1 ou const T1 . En 9.2p13, le deuxième membre T2 doit se situer après le premier membre (c’est-à-dire avec une adresse supérieure) et en 1.8p5, juste après le premier membre, de sorte que l’object soit contigu après la prise en compte de l’alignement (9.2p19).

Nous pouvons vérifier cela en utilisant offsetof (qui est défini pour les types de disposition standard):

 static_assert(offsetof(pair, second) == offsetof(pair, second), "!"); 

Puisque la pair et la pair ont la même disposition, la conversion dans le sens aller et l’utilisation du résultat pour accéder aux membres sont valides au 3.9.2p3:

Si un object de type T est situé à une adresse A , un pointeur de type cv T* dont la valeur est l’adresse A est censé pointer sur cet object, quelle que soit la manière dont la valeur a été obtenue.

Donc, la reinterpret_cast n’est sécurisée que si std::is_standard_layout>::value est true .

La réponse pratique est que le transtypage sur const doit être sûr car vous réinterprétez-transtyper un object avec une représentation identique. Cependant, l’inverse introduit un comportement indéfini (const to non-const).

En ce qui concerne la réponse “théorique”, il convient de noter que la norme C ++ ne garantit pas une représentation identique au niveau du bit des objects const / non-const. Le mot clé const garantit la “constance conceptuelle”, qui dépend de la mise en oeuvre.