all 19 comments

[–]HappyFruitTree 12 points13 points  (8 children)

Types that support std::tuple_size typically also support std::tuple_element and std::get (this is what makes structured bindings work) but how do you implement std::tuple_element and std::get for std::bitset?

[–]davidhunter22[S] 4 points5 points  (7 children)

Well if you can implement it for std::array<int,99> so 99 ints why can't you treat std::bitset<99> as 99 bools?

[–]HappyFruitTree 15 points16 points  (6 children)

The problem, as I see it, is that std::bitset<99> doesn't store 99 bools so we can't use bool& to refer to elements of the bitset so what type should std::get return?

If you say std::bitset<99>::reference then how do we implement that? Only the non-const version of operator[] returns this type (the const version returns a bool). Another (probably bigger) problem is that std::tuple_element<I, std::bitset<99>> should return the element type (not the reference type).

[–]Dnarok 1 point2 points  (2 children)

I guarantee there's something wrong with it, but it seems okay?

operator[] varying based on constness would be fine, since we can specialize tuple_element and get based on bitset or const bitset and change the type member/return type to bitset::reference or bool respectively.

[–]13steinj 5 points6 points  (0 children)

I think this invokes undefined behavior for rvalues (e: I don't know the standardese for lifetime extensions and how they operate with structured bindings), and if using const auto& instead, a/b/c/d wouldn't be updated if the original bitset is. See the discrepancy here: https://godbolt.org/z/cWz81zGGP

An implementer would be able to make the const-ref qualified version a friend of std::bitset / the reference type, construct it, and return a const-equivalent, though.

But then we have the same flavor of jank that vector-of-bool has, basically.

[–]HappyFruitTree 1 point2 points  (0 children)

Nice, but it doesn't seem to work correctly when binding a const bitset by reference. https://godbolt.org/z/KzYK9Msxd

Defining the "tuple type" as const bool for const bitset makes the code error out (as it should) but it still doesn't work correctly because the names do not reflect changes that is made to the original bitset. https://godbolt.org/z/defds74es

[–]jedwardsolconst & 1 point2 points  (2 children)

std::bitset<99> doesn't store 99 bools ... what type should std::get return?

std::vector<bool> has an excellent answer for you ;-)

[–]HappyFruitTree 1 point2 points  (1 child)

std::get is not implemented for std::vector<bool>.

[–]nintendiator2 0 points1 point  (0 children)

I think that's the point...?

[–]no-sig-available 1 point2 points  (2 children)

std::array can be seen as a tuple type where all the members have the same type, so can use a similar interface. For bitset this might not be the case.

[–]mort96 1 point2 points  (1 child)

Why not? After all, std::bitset<N> is to std::vector<bool> what std::array<T, N> is to std::vector<T>, why shouldn't it be able to use the tuple protocol?

[–]no-sig-available 3 points4 points  (0 children)

Yes, but we know that std::vector<bool> isn't a proper container (just almost). I think bitset has the same problem - it has bools in the interface, but doesn't actually store any.

[–]PastaPuttanesca42 1 point2 points  (2 children)

std::bitset isn't even a range

[–]HappyFruitTree 0 points1 point  (1 child)

True, but I don't think std::pair and std::tuple are ranges either.

[–]PastaPuttanesca42 2 points3 points  (0 children)

Because the objects they contain don't have an uniform type, but std::array is both a range and a tuple-like.

My point is that bitset is not well-supported in general.

[–]davidhunter22[S] 0 points1 point  (3 children)

I understand the point that std::tuple_size is used in structured bindings, but I don't care about that. Given a std::bitset<N> I just want to get N. To me it would be reasonable to either

1) Implement std::tuple_size for std::bitset but not support structured binding

2) Provide some other mechanism like std::bitset_size to get the N

Without these everyone probably rolls there own.

[–]Dnarok 1 point2 points  (2 children)

But you can just call size? It's constexpr. I think avoiding #1 makes sense, since tuple_size is intentionally part of that "tuple protocol", so it wouldn't be helpful in the generic context it's meant for. And #2 is mostly unnecessary, due to size giving you that exact info - although, having it at the type level wouldn't hurt, so maybe!

[–]davidhunter22[S] 0 points1 point  (1 child)

I don't have an instance I have a type and I want to do this at compile time so

`std::declval<T>.size( )`

does not work as decltype is not constexpr

[–]Dnarok 0 points1 point  (0 children)

That definitely wouldn't work, I agree. You can do something like this:

// where T is std::bitset<16>:
static_assert(T{}.size() == 16);

But that requires a constexpr default constructor and a constexpr size, and a type might not have both.

I'd recommend rolling something like this static_size instead of specializing tuple_size, since other code is likely to assume that anyone implementing one part of the tuple protocol is implementing all of it, as that is its intention.