Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't std::swap work on vector<bool> elements under Clang/Win?

I have code like this:

#include <vector>
#include <utility>

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

Arguments about the sanity of vector<bool> aside, this was working just fine on:

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

Then I tried building it with Clang on Windows and received the following error (abridged):

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

I'm surprised that the results differ across implementations.

Why does it not work with Clang on Windows?

like image 565
Lightness Races in Orbit Avatar asked Nov 01 '19 13:11

Lightness Races in Orbit


1 Answers

The standard doesn't require this to compile on any toolchain!

First recall that vector<bool> is weird and subscripting it gives you a temporary object of a proxy type called std::vector<bool>::reference, rather than an actual bool&.

The error message is telling you that it cannot bind this temporary to a non-const lvalue reference in the generic template <typename T> std::swap(T& lhs, T& rhs) implementation.

Extensions!

However, it turns out that libstdc++ defines an overload for std::swap(std::vector<bool>::reference, std::vector<bool>::reference), but this is an extension to the standard (or, if it is in there, I can't find any evidence for it).

libc++ does this too.

I'd guess that the Visual Studio stdlib implementation, which you're still using, doesn't, but then to add insult to injury you can bind temporaries to lvalue references in VS (unless you're using conformance mode), so the standard, "generic", std::swap function works until you substitute the VS compiler for the stricter Clang compiler.

As a result, you've been relying on extensions on all of the three toolchains for which it did work for you, and the Clang on Windows combination is the only one actually exhibiting strict compliance.

(In my opinion, those three toolchains should have diagnosed this so you didn't ship non-portable code this whole time. 😊)

What now?

It may be tempting to add your own specialisation of std::swap and std::vector<bool>::reference, but you're not allowed to do this for standard types; indeed, it would conflict with the overloads that libstdc++ and libc++ have chosen to add as extensions.

So, to be portable and compliant, you should change your code.

Perhaps a good old-fashioned:

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

Or make use of the special static member function that does exactly what you wanted:

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

Also spellable as follows:

vb.swap(vb[0], vb[1]);
like image 50
Lightness Races in Orbit Avatar answered Oct 05 '22 22:10

Lightness Races in Orbit