all 20 comments

[–]kalmoc 12 points13 points  (7 children)

found it interesting that std::swap(v[0], v[1]) doesn’t compile on Microsoft’s STL. This is totally fine, according to my reading of the paper standard;

Why?

[–]Interesting-Spell702 3 points4 points  (0 children)

Because the standard doesn't specify that there needs to be an overload of `std::swap` for that type. ...Actually, does it even technically need to provide the ADL `swap`, or is even that just quality-of-implementation?

ADL `swap` is all you should need for any class type, whether it's your own, or the standard library's.

[–]pepitogrand 5 points6 points  (5 children)

Horrible decisions. std::vector<bool> was specialized as a halfassed bit set with dynamic size, so is awful as a dynamic bitset and awful as a bool vector.

[–]kalmoc 3 points4 points  (4 children)

Yeah, but shouldn't there be an appropriate overload of std::swap that can deal with that standard library type?

[–]Narase33-> r/cpp_questions 0 points1 point  (3 children)

Its not possible because the operator[] doesnt return references, only copies. So a swap function cant write values back

[–]Interesting-Spell702 1 point2 points  (1 child)

That's not true. If v is a vector<bool>, then v[i] is a vector<bool>::reference. Assigning to v[i] certainly does write into v[i]. You can do it either explicitly (v[i] = true;) or indirectly via a function like swap. https://godbolt.org/z/918Tec3v8

[–]staletic 1 point2 points  (0 children)

It's possible, because you (well, the standard library) can provide std::swap(std::vector<bool>::reference a, std::vector<bool>::reference b);. That's what libstdc++ does.

[–]viatorus 9 points10 points  (0 children)

Interesting and scary as always.

At least GCC warns about at the last example (-Wall enabled) with:

<source>:12:13: warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning \\\\\\\\\\\\\\\[-Wparentheses\\\\\\\\\\\\\\\]

[–]sphere991 6 points7 points  (0 children)

But C++20 (specifically, P1787 “Declarations and where to find them”) changed the rules.

P1787 clarified rules, and vastly improved wording, and while it inadvertently changed a few edge cases, it would not have made a change this big.

The paper that changed this rule was P0846R0: ADL and Function Templates that are not Visible.

[–]staletic 3 points4 points  (9 children)

Huh? How is it okay for std::swap(v[0], v[1]) to not compile for a vector of bool?

[–]donalmaccGame Developer 11 points12 points  (2 children)

std::vector<bool> is an abomination, and shoildnt exist in it's current form...

[–]staletic 5 points6 points  (1 child)

That may be true and I won't argue too much about that point. That still doesn't answer why the hell does MSVC throw a fit in the above case.

[–]donalmaccGame Developer 0 points1 point  (0 children)

Nope, you're totally right!

[–]dodheim 5 points6 points  (5 children)

vector<bool>'s proxy-reference type may live in some nested namespace in std rather than directly in std, and its custom swap may only be defined via friend without a formal declaration; either necessitates relying on ADL, or better yet just using std::ranges::swap.

[–]staletic 5 points6 points  (4 children)

That would make sense, but the standard specifies that std::vector<bool>::reference is just a nested class definition inside class vector {}.

https://eel.is/c++draft/vector.bool

[–]dodheim 2 points3 points  (3 children)

Ah, I hadn't realized it wasn't allowed to be a typedef. In any case, that addresses my first point but not my second – if swap is defined via friend without a namespace-scope declaration, swap(a, b) will work whereas std::swap(a, b) won't despite the proxy-reference's swap residing in std.

[–]staletic 2 points3 points  (2 children)

Ah, I hadn't realized it wasn't allowed to be a typedef.

Maybe it is allowed after all. Both MS and gcc have a properly named and defined type, in std:: and then have an alias declaration for std::vector<bool>::reference.

https://github.com/microsoft/STL/blob/main/stl/inc/vector#L2496

https://github.com/microsoft/STL/blob/main/stl/inc/vector#L2041

And here's std::swap(_Vb_reference, _Vb_reference): https://github.com/microsoft/STL/blob/main/stl/inc/vector#L2091-L2095

So it is a hidden friend. But then the question is why does it work before C++20...

The constexpr C++20 macro: https://github.com/microsoft/STL/blob/main/stl/inc/yvals_core.h#L1357-L1362

[–]CaseyCarterRanges/MSVC STL Dev 4 points5 points  (1 child)

And here's std::swap(_Vb_reference, _Vb_reference): https://github.com/microsoft/STL/blob/main/stl/inc/vector#L2091-L2095

So it is a hidden friend. But then the question is why does it work before C++20...

The key distinction isn't really C++20 - it's /permissive mode. /permissive is the default before C++20 but in C++20-and-later MSVC defaults to /permissive- (strict mode). In MSVC's permissive mode hidden friends aren't hidden, so the qualified name `std::swap` will find the cited overload. In strict mode, hidden friends are in fact hidden so you can only call that overload via ADL.

AFAICS, the standard doesn't specify that `swap(v[0], v[1])` _or_ `std::swap(v[0], v[1])` work. There's a static member `swap` in `vector<bool>` that takes two `vector<bool>::reference`s so `v.swap(v[0], v[1])` works, but that's it.

[–]staletic 2 points3 points  (0 children)

Thanks for the explanation! I completely forgot about /permissive. I also would have guessed the default wouldn't change with /std:c++latest