Por que o std :: swap não funciona nos elementos do vetor <bool> no Clang / Win?

14

Eu tenho um código como este:

#include <vector>
#include <utility>

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

Argumentos sobre a sanidade de vector<bool>lado, isso estava funcionando bem:

  • Clang for Mac
  • Visual Studio para Windows
  • GCC para Linux

Tentei compilar com o Clang no Windows e recebi o seguinte erro (abreviado):

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>&&

Estou surpreso que os resultados sejam diferentes nas implementações.

Por que não funciona com o Clang no Windows?

Raças de leveza em órbita
fonte
Então, acho que o esclarecimento necessário é: O resultado de operator[]um lvalue? e pode std::swapoperar com rvalues ​​e xvalues?
#
@Mgetz Yes. Não. Nessa ordem. Essa pergunta foi feita "de verdade" em particular no outro dia, e eu achei suficientemente divertido que a resposta fosse "Clang / Win não está quebrado; o código foi quebrado o tempo todo, mas os combos principais da cadeia de ferramentas nunca se preocuparam em lhe dizer "para escrever aqui: P
Lightness Races in Orbit
2
Assim como um FYI, isso não compilar no VS 2019 com /permissive-(conformidade), que geralmente deve ser usada de qualquer maneira;)
ChrisMM
11
@ChrisMM Indeed! O modo de conformidade desativado fazia parte do quebra-cabeça. (Embora nós não sabia que antes de olhar para ele!) E minha resposta faz ponto que fora: P
Leveza raças na órbita

Respostas:

15

O padrão não exige que isso seja compilado em nenhuma cadeia de ferramentas!

Primeiro, lembre-se de que vector<bool>é esquisito e de assinatura que fornece um objeto temporário do tipo proxy chamado std::vector<bool>::reference, em vez de real bool&.

A mensagem de erro está informando que ele não pode vincular esse temporário a uma constreferência sem valor na template <typename T> std::swap(T& lhs, T& rhs)implementação genérica .

Extensões!

No entanto, acontece que o libstdc ++ define uma sobrecarga para std::swap(std::vector<bool>::reference, std::vector<bool>::reference), mas essa é uma extensão do padrão (ou, se estiver lá, não consigo encontrar nenhuma evidência para isso).

A libc ++ também faz isso .

Suponho que a implementação stdlib do Visual Studio, que você ainda está usando, não o faça , mas para adicionar insulto à lesão, você pode vincular temporariamente a referências de valor no VS (a menos que esteja usando o modo de conformidade). A função padrão "genérica" std::swapfunciona até você substituir o compilador VS pelo compilador Clang mais rigoroso.

Como resultado, você depende de extensões em todas as três cadeias de ferramentas para as quais funcionou para você, e a combinação Clang no Windows é a única que realmente exibe conformidade estrita.

(Na minha opinião, essas três cadeias de ferramentas deveriam ter diagnosticado isso para que você não enviasse código não portátil o tempo todo.)

E agora?

Pode ser tentador adicionar sua própria especialização de std::swape std::vector<bool>::reference, mas você não pode fazer isso para tipos padrão; de fato, entraria em conflito com as sobrecargas que libstdc ++ e libc ++ optaram por adicionar como extensões.

Portanto, para ser portátil e compatível, você deve alterar seu código .

Talvez um bom antiquado:

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

Ou use a função de membro estático especial que faz exatamente o que você queria :

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

Também soletrável da seguinte forma:

vb.swap(vb[0], vb[1]);
Raças de leveza em órbita
fonte
Em relação ao AFAIK, mas não deve, eles podem fazê-lo. Contanto que eles não quebrem o código em conformidade, eles podem estender a implementação para tornar o código quebrado "OK".
NathanOliver 01/11/19
@ NathanOliver-ReinstateMonica Bem, tudo bem. Eles não precisam ao menos diagnosticar o uso de tais coisas? eel.is/c++draft/intro.compliance#8
Leveza raças em órbita
@LightnessRaceswithMonica existe algum idioma que proíba esta extensão?
Mgetz
@Mgetz Desculpe, eu não sou versado em todas as línguas existentes, então não posso responder a isso
Leveza raças em órbita
Não tenho certeza se o uso de extensões mal formadas de acordo com este documento se aplica. Eles adicionaram uma sobrecarga que leva a std::vector<bool>::referenceque nada seja realmente mal formado. Para mim, parece que usar algo assim char * foo = "bar";exigiria um diagnóstico, pois isso é mal formado.
NathanOliver 01/11/19