Guss
Guss

Reputation: 950

Structured-binding and cv-/ref-qualifier propagation

Consider the following example (available here on Compiler Explorer), matching the 3 scenarios of structured-binding:

    {   // array
        auto value = std::array{ 1, 2 };
        auto & [ v1, v2 ] = value;
        static_assert(std::same_as<int, decltype(v1)>);
    }
    {   // tuple
        auto value = std::tuple{ 1, 2 };
        auto && [ v1, v2 ] = std::move(value);
        static_assert(std::same_as<int, decltype(v1)>);
    }
    {   // user-defined type
        struct toto{ int i; char c; };
        const auto value = toto{ 1, 2 };
        auto && [ v1, v2 ] = value;
        static_assert(std::same_as<const int, decltype(v1)>);
    }

I have troubles understanding why cv-qualifiers are propagated to the identifiers, but not the ref-qualifiers.

In the example above, I feel like it is kind of counter-intuitive that v1 is a possibly const-qualified int, but never ref-qualified.

What am I missing?

Upvotes: 4

Views: 131

Answers (1)

Adrian Mole
Adrian Mole

Reputation: 51835

Your three decltype(v1) specifiers all yield the referenced type of the v1 expression, which is int (with the const qualifier, in the third case). From cppreference:

If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type (described in the specification of the structured binding declaration).

However, if we add an extra set of parentheses around v1, we convert that to an 'ordinary' lvalue expression, which includes the reference qualifier. From the same cppreference page:

Note that if the name of an object is parenthesized, it is treated as an ordinary lvalue expression, thus decltype(x) and decltype((x)) are often different types.

Thus, just taking your first (array) case, the following assertion fails:

auto value = std::array{ 1, 2 };
auto& [v1, v2] = value;
static_assert(std::same_as<int, decltype((v1))>); // Fails: int != int&

But the assertion succeeds (again) if we add the reference qualifier to both sides of the test:

auto value = std::array{ 1, 2 };
auto& [v1, v2] = value;
static_assert(std::same_as<int&, decltype((v1))>); // OK: int& == int&

Note that, because the parenthesized version of v1 is an lvalue expression, in the second and third examples you give, decltype((v1)) will yield int& rather than int&&:

auto value = std::tuple{ 1, 2 };
auto&& [v1, v2] = std::move(value);
static_assert(std::same_as<int&&, decltype((v1))>); // Fails
static_assert(std::same_as<int&, decltype((v1))>);  // OK

Upvotes: 4

Related Questions