// structBind.cpp — Phase 2.7 cxxSmoke check 4.
//
// Exercises C++17 structured bindings.  Two flavours:
//   (a) direct binding of a plain aggregate (the "structured-binding-by-
//       element" form — no tuple_size/get<> machinery needed).
//   (b) tuple-style binding of a user type that opts in via std::tuple_size,
//       std::tuple_element, and a get<I>() free function.  This is the
//       customisation point that std::tie / std::pair / std::tuple all use.
//
//   $025000 = 0x0007       (aggregate first member)
//   $025002 = 0x002A       (aggregate second member)
//   $025004 = 0x00DE       (tuple-protocol member 0)
//   $025006 = 0x00AD       (tuple-protocol member 1)
//   $025008 = 0x0099       success marker
#include <stdint.h>
#include <stddef.h>


// We have no host libc++, so std::tuple_size / std::tuple_element are not
// declared anywhere by default.  Provide the primary class templates so
// the user-type specialisation below has something to specialise (and so
// clang's structured-binding lookup finds them).
namespace std {
    template <typename T> struct tuple_size;
    template <size_t I, typename T> struct tuple_element;
}


// Aggregate destructured by element (no protocol).
struct Pair {
    uint16_t a;
    uint16_t b;
};


// User type opting in to the tuple protocol for structured bindings.
struct Pt {
    uint16_t x;
    uint16_t y;
};


template <size_t I>
uint16_t get(const Pt &p) {
    if constexpr (I == 0) {
        return p.x;
    } else {
        return p.y;
    }
}


namespace std {
    template <> struct tuple_size<Pt> { static constexpr size_t value = 2; };
    template <> struct tuple_element<0, Pt> { using type = uint16_t; };
    template <> struct tuple_element<1, Pt> { using type = uint16_t; };
}


int main(void) {
    Pair p = { 0x0007, 0x002A };
    auto [pa, pb] = p;
    *(volatile uint16_t *)0x025000UL = pa;
    *(volatile uint16_t *)0x025002UL = pb;

    Pt q = { 0x00DE, 0x00AD };
    auto [qx, qy] = q;
    *(volatile uint16_t *)0x025004UL = qx;
    *(volatile uint16_t *)0x025006UL = qy;

    *(volatile uint16_t *)0x025008UL = 0x0099;
    return 0;
}
