2
#include <iostream>
#include <tuple>

struct my_struct {
    const std::tuple<int, float> t{5, 3.14};

    template<std::size_t i>
    decltype(std::get<i>(t)) get() {
        return std::get<i>(t);
    }
};

namespace std {
template<>
struct tuple_size<my_struct> :
    integral_constant<
        std::size_t,
        std::tuple_size_v<decltype(std::declval<my_struct>().t)>> {
};

template<std::size_t i>
struct tuple_element<i, my_struct> {
    using type = tuple_element_t<
        i,
        decltype(std::declval<my_struct>().t)>;
};
}

int main() {
    my_struct s;

    auto [x, y] = s;
    std::cout << (std::is_const_v<decltype(x)> ? "x is const" : "x is not const")
              << '\n';

    auto [z, w] = s.t;
    std::cout << (std::is_const_v<decltype(z)> ? "z is const" : "z is not const")
              << '\n';

    return 0;
}

This code prints

x is const
z is not const

Why is x const but z is not if I specialized std::tuple_element using the type of the struct t directly and defined get using the type returned by std::get<i>(t)?

How can I fix this problem (where fixing it means making my custom type behave exactly like the struct it contains when using structured bindings)?

1
  • 1
    How can I fix this problem Remove const from the member definition? Commented Apr 3 at 7:08

1 Answer 1

5

Why is x const but z is not

Because you used plain auto. Structured bindings are in terms of an object (referred to as "e" in the spec), that is initialized from the initializer you provide to the structured bindings declaration. The "aggregate" we specify as initializer isn't guaranteed to be the "aggregate" we bind names to.

The placeholder type determines how the "aggregate" (to whichs elements the names we declare may refer) expression is initialized.

auto [...] = s;  // Define "auto e = s;", a copy, bind names to the copy's elements (e is not s).
auto& [...] = s; // Define "auto& e = s;", a reference, the names are bound to "s" via "e".
auto const& [...] = s; // Similiar to before, but now overload resolution for `get` gets a const reference regardless of what `s ` was.

And so forth. You got different results because the copies you created are both non-const objects, so you compared apples to oranges. When copying s we still use the same machinary you provided for the non-const copy. But when we copy s.t we get a non-const tuple, so the names aren't to const objects either.

If we were to define

auto& [x, y] = s;
auto& [z, w] = s.t;

Then the const is deduced for the reference, and we'd get what you expect.

How can I fix this problem (where fixing it means making my custom type behave exactly like the struct it contains when using structured bindings)?

One option is to drop the idiosyncratic const from the member, as mentioned. If the tuple gets the const qualification from struct that holds it, it won't be as confusing.

Another is to make the member private, so it's impossible to get a binding without using your machinery. If you create custom machinery, it stands to reason we shouldn't expose implementation details.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.