Pourquoi std :: swap ne fonctionne-t-il pas sur les éléments vectoriels <bool> sous Clang / Win?

14

J'ai un code comme celui-ci:

#include <vector>
#include <utility>

int main()
{
   std::vector<bool> vb{true, false};
   std::swap(vb[0], vb[1]);
}

Arguments sur la raison de vector<bool>côté, cela fonctionnait très bien sur:

  • Clang pour Mac
  • Visual Studio pour Windows
  • GCC pour Linux

Ensuite, j'ai essayé de le construire avec Clang sur Windows et j'ai reçu l'erreur suivante (abrégée):

error: no matching function for call to 'swap'
                                std::swap(vb[0], vb[1]);
                                ^~~~~~~~~

note: candidate function [with _Ty = std::_Vb_reference<std::_Wrap_alloc<std::allocator<unsigned int> > >, $1 = void] not viable: expects an l-value for 1st argument
inline void swap(_Ty& _Left, _Ty& _Right) _NOEXCEPT_COND(is_nothrow_move_constructible_v<_Ty>&&

Je suis surpris que les résultats diffèrent selon les implémentations.

Pourquoi cela ne fonctionne-t-il pas avec Clang sous Windows?

Courses de légèreté en orbite
la source
Je suppose donc que la clarification nécessaire est la suivante: le résultat est-il operator[]une valeur l? et peut std::swapfonctionner sur rvalues ​​et xvalues?
Mgetz
@Mgetz Oui. Non, dans cet ordre. Cette question a été posée "pour de vrai" en privé l'autre jour, et j'ai trouvé suffisamment divertissant que la réponse était "Clang / Win n'est pas cassé; le code a été cassé tout ce temps mais les combos de la chaîne d'outils ne se sont jamais donné la peine de vous dire "pour l'écrire ici: P
Courses de légèreté en orbite
2
Tout comme un FYI, cela ne se compile pas dans VS 2019 avec /permissive-(conformité), qui devrait généralement être utilisé de toute façon;)
ChrisMM
1
@ChrisMM Effectivement! Le mode de conformité désactivé faisait partie du puzzle. (Bien que nous ne le sachions pas avant de l'examiner!) Et ma réponse le souligne: P
Courses de légèreté en orbite

Réponses:

15

La norme n'exige pas cela pour compiler sur n'importe quelle chaîne d' outils!

Rappelez-vous d'abord que vector<bool>c'est bizarre et que l'indexation vous donne un objet temporaire d'un type proxy appelé std::vector<bool>::reference, plutôt qu'un réel bool&.

Le message d'erreur vous indique qu'il ne peut pas lier ce temporaire à une constréférence non lvalue dans l' template <typename T> std::swap(T& lhs, T& rhs)implémentation générique .

Extensions!

Cependant, il s'avère que libstdc ++ définit une surcharge pour std::swap(std::vector<bool>::reference, std::vector<bool>::reference), mais c'est une extension de la norme (ou, si elle est là-dedans, je ne trouve aucune preuve pour cela).

libc ++ fait aussi cela .

Je suppose que l'implémentation stdlib de Visual Studio, que vous utilisez toujours, ne fonctionne pas , mais pour ajouter une insulte à une blessure, vous pouvez lier des références temporaires à des références de valeur dans VS (sauf si vous utilisez le mode de conformité), de sorte que le la fonction standard, "générique", std::swapfonctionne jusqu'à ce que vous remplaciez le compilateur VS par le compilateur Clang plus strict.

En conséquence, vous vous êtes appuyé sur des extensions sur les trois chaînes d'outils pour lesquelles cela a fonctionné pour vous, et la combinaison Clang sur Windows est la seule à afficher une stricte conformité.

(À mon avis, ces trois chaînes d'outils auraient dû diagnostiquer cela afin que vous n'ayez pas expédié de code non portable pendant tout ce temps. 😊)

Et maintenant?

Il peut être tentant d'ajouter votre propre spécialisation de std::swapet std::vector<bool>::reference, mais vous n'êtes pas autorisé à le faire pour les types standard; en effet, cela entrerait en conflit avec les surcharges que libstdc ++ et libc ++ ont choisi d'ajouter en tant qu'extensions.

Donc, pour être portable et conforme, vous devez changer votre code .

Peut-être un bon vieux jeu:

const bool temp = vb[0];
vb[0] = vb[1];
vb[1] = temp;

Ou utilisez la fonction membre statique spéciale qui fait exactement ce que vous vouliez :

std::vector<bool>::swap(vb[0], vb[1]);

Également épelable comme suit:

vb.swap(vb[0], vb[1]);
Courses de légèreté en orbite
la source
En ce qui concerne l' AFAIK, mais ce n'est pas censé, ils sont autorisés à le faire. Tant qu'ils ne cassent pas le code conforme, ils peuvent étendre l'implémentation pour rendre le code cassé "OK".
NathanOliver
@ NathanOliver-ReinstateMonica Eh bien, d'accord. Ne doivent-ils pas au moins diagnostiquer l'utilisation de telles choses, cependant? eel.is/c++draft/intro.compliance#8
Courses de légèreté en orbite
@LightnessRaceswithMonica y a-t-il un langage qui interdit cette extension?
Mgetz
@Mgetz Désolé, je ne suis pas familier dans toutes les langues existantes, donc je ne peux pas répondre à cela
Courses de légèreté en orbite
Je ne sais pas si l' utilisation de telles extensions mal formées selon ce document s'applique. Ils ont ajouté une surcharge qui prend std::vector<bool>::referencedonc rien n'est mal formé. Pour moi, cela ressemble à l'utilisation de quelque chose char * foo = "bar";qui nécessiterait un diagnostic car il est mal formé.
NathanOliver