c++c++17language-lawyerqualifiersstructured-bindings

cv-qualifier propagation in structured binding


As quoted in dcl.struct.bind,

Let cv denote the cv-qualifiers in the decl-specifier-seq.

Designating the non-static data members of E as m 0 , m 1 , m 2 , ... (in declaration order), each v i is the name of an lvalue that refers to the member m i of e and whose type is cv T i , where T i is the declared type of that member;

If I'm understanding correctly, the cv-qualifiers are propagated from the declartion of structured binding.

Say I have a simple struct,

struct Foo {
    int x;
    double y;
};

Consider the two scenarios,

const Foo f{1, 1.0};
auto& [x, y] = f;
// static_assert(std::is_same<decltype(x), int>::value); // Fails!
static_assert(std::is_same<decltype(x), const int>::value); // Succeeds

Live Demo. Does the cv-qualifier of x come from the deduction auto?

The second one,

Foo f{1, 1.0};

const auto& [x, y] = f;
const auto& rf = f;

static_assert(std::is_same<decltype(x), const int>::value); // with const
static_assert(std::is_same<decltype(rf.x), int>::value); // without const

Live Demo. The result complies with the standard, which makes sense.

My second question is is there any reason to propagate the cv-qualifiers, isn't it a kind of inconsistent (to the initialization a reference with auto)?


Solution

  • decltype has a special rule when the member of a class is named directly as an unparenthesized member access expression. Instead of producing the result it would usually if the expression was treated as an expression, it will result in the declared type of the member.

    So decltype(rf.x) gives int, because x is declared as int. You can force decltype to behave as it would for other expressions by putting extra parentheses (decltype((rf.x))), in which case it will give const int& since it is an lvalue expression and an access through a const reference.

    Similarly there are special rules for decltype if a structured binding is named directly (without parentheses), which is why you don't get const int& for decltype(x).

    However the rules for structured bindings take the type from the member access expression as an expression if the member is not a reference type, which is why const is propagated. At least that is the case since the post-C++20 resolution of CWG issue 2312 which intends to make the const propagation work correctly with mutable members.

    Before the resolution the type of the structured binding was actually specified to just be the declared type of the member with the cv-qualifiers of the structured binding declaration added, as you are quoting in your question.

    I might be missing some detail on what declared type refers to exactly, but it seems to me that this didn't actually specify x to have type const int& in your first snippet (and decltype hence also not const), although that seems to be how all compilers always handled that case and is also the only behavior that makes sense. Maybe it was another defect, silently or unintentionally fixed by CWG 2312.

    So, practically speaking, both rf.x and x in your example are const int lvalue expressions when you use them as expressions. The only oddity here is in how decltype behaves.