Search code examples
c++c++17structured-bindings

Structured binding and tie()


Given these declarations:

int a[3] {10,20,30};
std::tuple<int,int,int> b {11,22,33};

I can use structured binding declarations to decode a and b:

auto [x1,y1,z1] = a;
auto [x2,y2,z2] = b;

But if x1, y1, etc. already exist, what do I do?

std::tie(x1,y1,z1) = a;  // ERROR
std::tie(x2,y2,z2) = b;  // OK

This works for b but not for a. Is there a similar simple construct that works for a, or do I have to fetch a[0], a[1] and a[2] separately?


Solution

  • Nope.

    Structured bindings has specific language rules to handle arrays and certain other types. tie() is specifically a tuple<T&...> and can only be assigned from another tuple<U&...>.


    With the array case, you can write a function to turn that array into a tuple of references into it:

    template <typename T, size_t N, size_t... Is>
    auto as_tuple_impl(T (&arr)[N], std::index_sequence<Is...>) {
        return std::forward_as_tuple(arr[Is]...);
    }
    
    template <typename T, size_t N>
    auto as_tuple(T (&arr)[N]) {
        return as_tuple_impl(arr, std::make_index_sequence<N>{});
    }
    
    std::tie(x1, y1, z1) = as_tuple(a); // ok
    

    Alternatively, if you know how many bindings there are (which you have to anyway), you can use structured bindings as give back a tuple. But you have to both specify the size and write out a case for each one:

    template <size_t I, typename T>
    auto as_tuple(T&& tuple) {
        if constexpr (I == 1) {
            auto&& [a] = std::forward<T>(tuple);
            return std::forward_as_tuple(a);
        } else if constexpr (I == 2) {
            auto&& [a, b] = std::forward<T>(tuple);
            return std::forward_as_tuple(a, b);
        } else if constexpr (I == 3) {
            // etc.
        }
    }
    
    std::tie(x1, y1, z1) = as_tuple<3>(a); // ok