Why does including break structured bind

2020-06-30 08:10发布

问题:

Consider:

struct Point { int x, y; };

int main()
{
    const auto [x, y] = Point{};
}

This code compiles fine with gcc 7.1 in C++17 mode, however this one:

#include <utility>

struct Point { int x, y; };

int main()
{
    const auto [x, y] = Point{};
}

gives an error:

bug.cpp: In function 'int main()':
bug.cpp:7:16: error: 'std::tuple_size<const Point>::value' is not an integral constant expression
     const auto [x, y] = Point{};
                ^~~~~~

What's going on here? A compiler bug, or is this how structured bindings are supposed to work?

回答1:

This is compiler bug 78939. Although it's a bit more complicated than that - there were a few issues between the core language and the library that were mutually contradictory (GB 20, LWG 2770, and LWG 2446), which lead to the kind of behavior that gcc/libstdc++ exhibit here. It is certainly intended that the code work with or without #include <utility>, it's just a matter of the standard wording having gotten there properly.


Yes, classes with all public non-anonymous union members should be usable in structured bindings declarations per [dcl.struct.bind]/4:

Otherwise, all of E's non-static data members shall be public direct members of E or of the same unambiguous public base class of E, E shall not have an anonymous union member, and the number of elements in the identifier-list shall be equal to the number of non-static data members of E. Designating the non-static data members of E as m0, m1, m2, ... (in declaration order), each vi is the name of an lvalue that refers to the member mi of e and whose type is cv Ti, where Ti is the declared type of that member; the referenced type is cv Ti. The lvalue is a bit-field if that member is a bit-field. [ Example:

struct S { int x1 : 2; volatile double y1; };
S f();
const auto [ x, y ] = f();

This is completely unrelated to the inclusion of <utility>, nothing in this code depends on any library functionality - the members are grabbed directly, and not via the get/tuple_size mechanism.



回答2:

The core idea behind structured bindings is that std::tuple_size<T> defines how many components you get from unpacking T, and T::get<N> should access the N'th element. Not surprising, this std::tuple_size<T> is a specialization from the base template in <utility>.

Now in this case, Point doesn't have such support for structured bindings, but it is a special case (all public non-static members) for which C++17 states that no special unpacking support is needed. This is an exception to the rule above.

The compiler is tripping over itself here, and trying to use the generic rule when it sees the unspecialized std::tuple_size from <utility>.