Message ID | 20231114161044.985367-1-ppalka@redhat.com |
---|---|
State | New |
Headers | show |
Series | c++: decltype of (non-captured variable) [PR83167] | expand |
On 11/14/23 11:10, Patrick Palka wrote: > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > trunk? > > -- >8 -- > > For decltype((x)) within a lambda where x is not captured, we dubiously > require that the lambda has a capture default, unlike for decltype(x). > This patch fixes this inconsistency; I couldn't find a justification for > it in the standard. The relevant passage seems to be https://eel.is/c++draft/expr.prim#id.unqual-3 "If naming the entity from outside of an unevaluated operand within S would refer to an entity captured by copy in some intervening lambda-expression, then let E be the innermost such lambda-expression. If there is such a lambda-expression and if P is in E's function parameter scope but not its parameter-declaration-clause, then the type of the expression is the type of a class member access expression ([expr.ref]) naming the non-static data member that would be declared for such a capture in the object parameter ([dcl.fct]) of the function call operator of E." In this case I guess there is no such lambda-expression because naming x won't refer to a capture by copy if the lambda doesn't capture anything, so we ignore the lambda. Maybe refer to that in a comment? OK with that change. I'm surprised that it refers specifically to capture by copy, but I guess a capture by reference should have the same decltype as the captured variable? Jason > PR c++/83167 > > gcc/cp/ChangeLog: > > * semantics.cc (finish_decltype_type): If capture_decltype > returns NULL_TREE, fall back to the ordinary code path. > (capture_decltype): Return NULL_TREE if the lambda has no > capture-default. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp0x/lambda/lambda-decltype4.C: New test. > --- > gcc/cp/semantics.cc | 6 +++--- > .../g++.dg/cpp0x/lambda/lambda-decltype4.C | 15 +++++++++++++++ > 2 files changed, 18 insertions(+), 3 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc > index 8090c71809f..6fdd6c45972 100644 > --- a/gcc/cp/semantics.cc > +++ b/gcc/cp/semantics.cc > @@ -11732,7 +11732,8 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, > /* If the expression is just "this", we want the > cv-unqualified pointer for the "this" type. */ > type = TYPE_MAIN_VARIANT (TREE_TYPE (expr)); > - else > + > + if (!type) > { > /* Otherwise, where T is the type of e, if e is an lvalue, > decltype(e) is defined as T&; if an xvalue, T&&; otherwise, T. */ > @@ -12639,8 +12640,7 @@ capture_decltype (tree decl) > switch (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lam)) > { > case CPLD_NONE: > - error ("%qD is not captured", decl); > - return error_mark_node; > + return NULL_TREE; > > case CPLD_COPY: > type = TREE_TYPE (decl); > diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C > new file mode 100644 > index 00000000000..0062d7b8672 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C > @@ -0,0 +1,15 @@ > +// PR c++/83167 > +// { dg-do compile { target c++11 } } > + > +int main() { > + int x; > + const int y = 42; > + > + [] { > + using ty1 = decltype((x)); > + using ty1 = int&; > + > + using ty2 = decltype((y)); > + using ty2 = const int&; > + }; > +}
On Tue, 14 Nov 2023, Jason Merrill wrote: > On 11/14/23 11:10, Patrick Palka wrote: > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > > trunk? > > > > -- >8 -- > > > > For decltype((x)) within a lambda where x is not captured, we dubiously > > require that the lambda has a capture default, unlike for decltype(x). > > This patch fixes this inconsistency; I couldn't find a justification for > > it in the standard. > > The relevant passage seems to be > > https://eel.is/c++draft/expr.prim#id.unqual-3 > > "If naming the entity from outside of an unevaluated operand within S would > refer to an entity captured by copy in some intervening lambda-expression, > then let E be the innermost such lambda-expression. > > If there is such a lambda-expression and if P is in E's function parameter > scope but not its parameter-declaration-clause, then the type of the > expression is the type of a class member access expression ([expr.ref]) naming > the non-static data member that would be declared for such a capture in the > object parameter ([dcl.fct]) of the function call operator of E." > > In this case I guess there is no such lambda-expression because naming x won't > refer to a capture by copy if the lambda doesn't capture anything, so we > ignore the lambda. > > Maybe refer to that in a comment? OK with that change. > > I'm surprised that it refers specifically to capture by copy, but I guess a > capture by reference should have the same decltype as the captured variable? Ah, seems like it. So maybe we should get rid of the redundant by-reference capture-default handling, to more closely mirror the standard? Also now that r14-6026-g73e2bdbf9bed48 made capture_decltype return NULL_TREE to mean the capture is dependent, it seems we should just inline capture_decltype into finish_decltype_type rather than introducing another special return value to mean "fall back to ordinary handling". How does the following look? Bootstrapped and regtested on x86_64-pc-linux-gnu. -- >8 -- PR c++/83167 gcc/cp/ChangeLog: * semantics.cc (capture_decltype): Inline into its only caller ... (finish_decltype_type): ... here. Update nearby comment to refer to recent standard. Restrict uncaptured variable handling to just lambdas with a by-copy capture-default. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/lambda/lambda-decltype4.C: New test. --- gcc/cp/semantics.cc | 107 +++++++----------- .../g++.dg/cpp0x/lambda/lambda-decltype4.C | 15 +++ 2 files changed, 55 insertions(+), 67 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index fbbc18336a0..fb4c3992e34 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -53,7 +53,6 @@ along with GCC; see the file COPYING3. If not see static tree maybe_convert_cond (tree); static tree finalize_nrv_r (tree *, int *, void *); -static tree capture_decltype (tree); /* Used for OpenMP non-static data member privatization. */ @@ -11856,21 +11855,48 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, } else { - /* Within a lambda-expression: - - Every occurrence of decltype((x)) where x is a possibly - parenthesized id-expression that names an entity of - automatic storage duration is treated as if x were - transformed into an access to a corresponding data member - of the closure type that would have been declared if x - were a use of the denoted entity. */ if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr)) && current_function_decl && LAMBDA_FUNCTION_P (current_function_decl)) { - type = capture_decltype (STRIP_REFERENCE_REF (expr)); - if (!type) - goto dependent; + /* [expr.prim.id.unqual]/3: If naming the entity from outside of an + unevaluated operand within S would refer to an entity captured by + copy in some intervening lambda-expression, then let E be the + innermost such lambda-expression. + + If there is such a lambda-expression and if P is in E's function + parameter scope but not its parameter-declaration-clause, then the + type of the expression is the type of a class member access + expression naming the non-static data member that would be declared + for such a capture in the object parameter of the function call + operator of E." */ + tree decl = STRIP_REFERENCE_REF (expr); + tree lam = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (current_function_decl)); + tree cap = lookup_name (DECL_NAME (decl), LOOK_where::BLOCK, + LOOK_want::HIDDEN_LAMBDA); + + if (cap && is_capture_proxy (cap)) + type = TREE_TYPE (cap); + else if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lam) == CPLD_COPY) + { + type = TREE_TYPE (decl); + if (TYPE_REF_P (type) + && TREE_CODE (TREE_TYPE (type)) != FUNCTION_TYPE) + type = TREE_TYPE (type); + } + + if (type && !TYPE_REF_P (type)) + { + tree obtype = TREE_TYPE (DECL_ARGUMENTS (current_function_decl)); + if (WILDCARD_TYPE_P (non_reference (obtype))) + /* We don't know what the eventual obtype quals will be. */ + goto dependent; + int quals = cp_type_quals (type); + if (INDIRECT_TYPE_P (obtype)) + quals |= cp_type_quals (TREE_TYPE (obtype)); + type = cp_build_qualified_type (type, quals); + type = build_reference_type (type); + } } else if (error_operand_p (expr)) type = error_mark_node; @@ -11878,7 +11904,8 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, /* If the expression is just "this", we want the cv-unqualified pointer for the "this" type. */ type = TYPE_MAIN_VARIANT (TREE_TYPE (expr)); - else + + if (!type) { /* Otherwise, where T is the type of e, if e is an lvalue, decltype(e) is defined as T&; if an xvalue, T&&; otherwise, T. */ @@ -12767,60 +12794,6 @@ apply_deduced_return_type (tree fco, tree return_type) } } -/* DECL is a local variable or parameter from the surrounding scope of a - lambda-expression. Returns the decltype for a use of the capture field - for DECL even if it hasn't been captured yet. Or NULL_TREE if we can't give - a correct answer at this point and we should build a DECLTYPE_TYPE. */ - -static tree -capture_decltype (tree decl) -{ - tree lam = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (current_function_decl)); - tree cap = lookup_name (DECL_NAME (decl), LOOK_where::BLOCK, - LOOK_want::HIDDEN_LAMBDA); - tree type; - - if (cap && is_capture_proxy (cap)) - type = TREE_TYPE (cap); - else - switch (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lam)) - { - case CPLD_NONE: - error ("%qD is not captured", decl); - return error_mark_node; - - case CPLD_COPY: - type = TREE_TYPE (decl); - if (TYPE_REF_P (type) - && TREE_CODE (TREE_TYPE (type)) != FUNCTION_TYPE) - type = TREE_TYPE (type); - break; - - case CPLD_REFERENCE: - type = TREE_TYPE (decl); - if (!TYPE_REF_P (type)) - type = build_reference_type (TREE_TYPE (decl)); - break; - - default: - gcc_unreachable (); - } - - if (!TYPE_REF_P (type)) - { - tree obtype = TREE_TYPE (DECL_ARGUMENTS (current_function_decl)); - if (WILDCARD_TYPE_P (non_reference (obtype))) - /* We don't know what the eventual obtype quals will be. */ - return NULL_TREE; - int quals = cp_type_quals (type); - if (INDIRECT_TYPE_P (obtype)) - quals |= cp_type_quals (TREE_TYPE (obtype)); - type = cp_build_qualified_type (type, quals); - type = build_reference_type (type); - } - return type; -} - /* Build a unary fold expression of EXPR over OP. If IS_RIGHT is true, this is a right unary fold. Otherwise it is a left unary fold. */ diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C new file mode 100644 index 00000000000..0062d7b8672 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C @@ -0,0 +1,15 @@ +// PR c++/83167 +// { dg-do compile { target c++11 } } + +int main() { + int x; + const int y = 42; + + [] { + using ty1 = decltype((x)); + using ty1 = int&; + + using ty2 = decltype((y)); + using ty2 = const int&; + }; +}
On 12/1/23 12:32, Patrick Palka wrote: > On Tue, 14 Nov 2023, Jason Merrill wrote: > >> On 11/14/23 11:10, Patrick Palka wrote: >>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for >>> trunk? >>> >>> -- >8 -- >>> >>> For decltype((x)) within a lambda where x is not captured, we dubiously >>> require that the lambda has a capture default, unlike for decltype(x). >>> This patch fixes this inconsistency; I couldn't find a justification for >>> it in the standard. >> >> The relevant passage seems to be >> >> https://eel.is/c++draft/expr.prim#id.unqual-3 >> >> "If naming the entity from outside of an unevaluated operand within S would >> refer to an entity captured by copy in some intervening lambda-expression, >> then let E be the innermost such lambda-expression. >> >> If there is such a lambda-expression and if P is in E's function parameter >> scope but not its parameter-declaration-clause, then the type of the >> expression is the type of a class member access expression ([expr.ref]) naming >> the non-static data member that would be declared for such a capture in the >> object parameter ([dcl.fct]) of the function call operator of E." >> >> In this case I guess there is no such lambda-expression because naming x won't >> refer to a capture by copy if the lambda doesn't capture anything, so we >> ignore the lambda. >> >> Maybe refer to that in a comment? OK with that change. >> >> I'm surprised that it refers specifically to capture by copy, but I guess a >> capture by reference should have the same decltype as the captured variable? > > Ah, seems like it. So maybe we should get rid of the redundant > by-reference capture-default handling, to more closely mirror the > standard? > > Also now that r14-6026-g73e2bdbf9bed48 made capture_decltype return > NULL_TREE to mean the capture is dependent, it seems we should just > inline capture_decltype into finish_decltype_type rather than > introducing another special return value to mean "fall back to ordinary > handling". > > How does the following look? Bootstrapped and regtested on > x86_64-pc-linux-gnu. > > -- >8 -- > > PR c++/83167 > > gcc/cp/ChangeLog: > > * semantics.cc (capture_decltype): Inline into its only caller ... > (finish_decltype_type): ... here. Update nearby comment to refer > to recent standard. Restrict uncaptured variable handling to just > lambdas with a by-copy capture-default. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp0x/lambda/lambda-decltype4.C: New test. > --- > gcc/cp/semantics.cc | 107 +++++++----------- > .../g++.dg/cpp0x/lambda/lambda-decltype4.C | 15 +++ > 2 files changed, 55 insertions(+), 67 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc > index fbbc18336a0..fb4c3992e34 100644 > --- a/gcc/cp/semantics.cc > +++ b/gcc/cp/semantics.cc > @@ -53,7 +53,6 @@ along with GCC; see the file COPYING3. If not see > > static tree maybe_convert_cond (tree); > static tree finalize_nrv_r (tree *, int *, void *); > -static tree capture_decltype (tree); > > /* Used for OpenMP non-static data member privatization. */ > > @@ -11856,21 +11855,48 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, > } > else > { > - /* Within a lambda-expression: > - > - Every occurrence of decltype((x)) where x is a possibly > - parenthesized id-expression that names an entity of > - automatic storage duration is treated as if x were > - transformed into an access to a corresponding data member > - of the closure type that would have been declared if x > - were a use of the denoted entity. */ > if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr)) > && current_function_decl > && LAMBDA_FUNCTION_P (current_function_decl)) > { > - type = capture_decltype (STRIP_REFERENCE_REF (expr)); > - if (!type) > - goto dependent; > + /* [expr.prim.id.unqual]/3: If naming the entity from outside of an > + unevaluated operand within S would refer to an entity captured by > + copy in some intervening lambda-expression, then let E be the > + innermost such lambda-expression. > + > + If there is such a lambda-expression and if P is in E's function > + parameter scope but not its parameter-declaration-clause, then the > + type of the expression is the type of a class member access > + expression naming the non-static data member that would be declared > + for such a capture in the object parameter of the function call > + operator of E." */ Hmm, looks like this code is only checking the innermost lambda, it needs to check all containing lambdas for one that would capture it by copy. > + tree decl = STRIP_REFERENCE_REF (expr); > + tree lam = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (current_function_decl)); > + tree cap = lookup_name (DECL_NAME (decl), LOOK_where::BLOCK, > + LOOK_want::HIDDEN_LAMBDA); > + > + if (cap && is_capture_proxy (cap)) > + type = TREE_TYPE (cap); > + else if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lam) == CPLD_COPY) > + { > + type = TREE_TYPE (decl); > + if (TYPE_REF_P (type) > + && TREE_CODE (TREE_TYPE (type)) != FUNCTION_TYPE) > + type = TREE_TYPE (type); > + } > + > + if (type && !TYPE_REF_P (type)) > + { > + tree obtype = TREE_TYPE (DECL_ARGUMENTS (current_function_decl)); > + if (WILDCARD_TYPE_P (non_reference (obtype))) > + /* We don't know what the eventual obtype quals will be. */ > + goto dependent; > + int quals = cp_type_quals (type); > + if (INDIRECT_TYPE_P (obtype)) > + quals |= cp_type_quals (TREE_TYPE (obtype)); > > Shouldn't we propagate cv-quals of a by-value object parameter as well? Ah, I think you're right. Jason
On Fri, 1 Dec 2023, Jason Merrill wrote: > On 12/1/23 12:32, Patrick Palka wrote: > > On Tue, 14 Nov 2023, Jason Merrill wrote: > > > > > On 11/14/23 11:10, Patrick Palka wrote: > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > > > > trunk? > > > > > > > > -- >8 -- > > > > > > > > For decltype((x)) within a lambda where x is not captured, we dubiously > > > > require that the lambda has a capture default, unlike for decltype(x). > > > > This patch fixes this inconsistency; I couldn't find a justification for > > > > it in the standard. > > > > > > The relevant passage seems to be > > > > > > https://eel.is/c++draft/expr.prim#id.unqual-3 > > > > > > "If naming the entity from outside of an unevaluated operand within S > > > would > > > refer to an entity captured by copy in some intervening lambda-expression, > > > then let E be the innermost such lambda-expression. > > > > > > If there is such a lambda-expression and if P is in E's function parameter > > > scope but not its parameter-declaration-clause, then the type of the > > > expression is the type of a class member access expression ([expr.ref]) > > > naming > > > the non-static data member that would be declared for such a capture in > > > the > > > object parameter ([dcl.fct]) of the function call operator of E." > > > > > > In this case I guess there is no such lambda-expression because naming x > > > won't > > > refer to a capture by copy if the lambda doesn't capture anything, so we > > > ignore the lambda. > > > > > > Maybe refer to that in a comment? OK with that change. > > > > > > I'm surprised that it refers specifically to capture by copy, but I guess > > > a > > > capture by reference should have the same decltype as the captured > > > variable? > > > > Ah, seems like it. So maybe we should get rid of the redundant > > by-reference capture-default handling, to more closely mirror the > > standard? > > > > Also now that r14-6026-g73e2bdbf9bed48 made capture_decltype return > > NULL_TREE to mean the capture is dependent, it seems we should just > > inline capture_decltype into finish_decltype_type rather than > > introducing another special return value to mean "fall back to ordinary > > handling". > > > > How does the following look? Bootstrapped and regtested on > > x86_64-pc-linux-gnu. > > > > -- >8 -- > > > > PR c++/83167 > > > > gcc/cp/ChangeLog: > > > > * semantics.cc (capture_decltype): Inline into its only caller ... > > (finish_decltype_type): ... here. Update nearby comment to refer > > to recent standard. Restrict uncaptured variable handling to just > > lambdas with a by-copy capture-default. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/cpp0x/lambda/lambda-decltype4.C: New test. > > --- > > gcc/cp/semantics.cc | 107 +++++++----------- > > .../g++.dg/cpp0x/lambda/lambda-decltype4.C | 15 +++ > > 2 files changed, 55 insertions(+), 67 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C > > > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc > > index fbbc18336a0..fb4c3992e34 100644 > > --- a/gcc/cp/semantics.cc > > +++ b/gcc/cp/semantics.cc > > @@ -53,7 +53,6 @@ along with GCC; see the file COPYING3. If not see > > static tree maybe_convert_cond (tree); > > static tree finalize_nrv_r (tree *, int *, void *); > > -static tree capture_decltype (tree); > > /* Used for OpenMP non-static data member privatization. */ > > @@ -11856,21 +11855,48 @@ finish_decltype_type (tree expr, bool > > id_expression_or_member_access_p, > > } > > else > > { > > - /* Within a lambda-expression: > > - > > - Every occurrence of decltype((x)) where x is a possibly > > - parenthesized id-expression that names an entity of > > - automatic storage duration is treated as if x were > > - transformed into an access to a corresponding data member > > - of the closure type that would have been declared if x > > - were a use of the denoted entity. */ > > if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr)) > > && current_function_decl > > && LAMBDA_FUNCTION_P (current_function_decl)) > > { > > - type = capture_decltype (STRIP_REFERENCE_REF (expr)); > > - if (!type) > > - goto dependent; > > + /* [expr.prim.id.unqual]/3: If naming the entity from outside of an > > + unevaluated operand within S would refer to an entity captured by > > + copy in some intervening lambda-expression, then let E be the > > + innermost such lambda-expression. > > + > > + If there is such a lambda-expression and if P is in E's function > > + parameter scope but not its parameter-declaration-clause, then > > the > > + type of the expression is the type of a class member access > > + expression naming the non-static data member that would be > > declared > > + for such a capture in the object parameter of the function call > > + operator of E." */ > > Hmm, looks like this code is only checking the innermost lambda, it needs to > check all containing lambdas for one that would capture it by copy. Unfortunately this seems to be a can of worms, since IIUC we also have to check that there's no non-default-capture lambda in the stack as well, e.g. int main() { int x; [] { [=] { using ty1 = decltype((x)); // refers to local variable despite // innermost by-copy capture-default using ty1 = int&; }; }; [=] { [] { using ty1 = decltype((x)); // same using ty1 = int&; }; }; [=] { [&] { using ty1 = decltype((x)); // refers to hypothetical capture proxy using ty1 = const int&; }; }; [&] { [=] { using ty1 = decltype((x)); // same using ty1 = const int&; }; }; } And we have to refine the logic for whether to perform the HIDDEN_LAMBDA name lookup (which we currently unconditionally do): int main() { int x; [x] { [x] { using ty1 = decltype((x)); // refers to actual capture proxy, // found by HIDDEN_LAMBDA name lookup using ty1 = const int&; }; }; [x] { [] { using ty1 = decltype((x)); // refers to local variable, // HIDDEN_LAMBDA name lookup not performed using ty1 = int&; }; }; } These could probably be fixed locally within finish_decltype_type, but then there's PR86697 which basically extends all of these capture-related issues to 'decltype(f(x))' instead of 'decltype((x))', which suggests a proper fix should probably be in process_outer_var_ref instead of in finish_decltype_type? Perhaps when in an unevaluated context, process_outer_var_ref should still rewrite uses into capture proxies but not actually add them to the closure or something like that? I don't think I have the cycles to work on these issues this stage.. Would the latest patch be OK at least? It seems to be a strict improvement. > > > + tree decl = STRIP_REFERENCE_REF (expr); > > + tree lam = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT > > (current_function_decl)); > > + tree cap = lookup_name (DECL_NAME (decl), LOOK_where::BLOCK, > > + LOOK_want::HIDDEN_LAMBDA); > > + > > + if (cap && is_capture_proxy (cap)) > > + type = TREE_TYPE (cap); > > + else if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lam) == CPLD_COPY) > > + { > > + type = TREE_TYPE (decl); > > + if (TYPE_REF_P (type) > > + && TREE_CODE (TREE_TYPE (type)) != FUNCTION_TYPE) > > + type = TREE_TYPE (type); > > + } > > + > > + if (type && !TYPE_REF_P (type)) > > + { > > + tree obtype = TREE_TYPE (DECL_ARGUMENTS > > (current_function_decl)); > > + if (WILDCARD_TYPE_P (non_reference (obtype))) > > + /* We don't know what the eventual obtype quals will be. */ > > + goto dependent; > > + int quals = cp_type_quals (type); > > + if (INDIRECT_TYPE_P (obtype)) > > + quals |= cp_type_quals (TREE_TYPE (obtype)); > > > > Shouldn't we propagate cv-quals of a by-value object parameter as well? > > Ah, I think you're right. > > Jason > >
On 12/1/23 17:42, Patrick Palka wrote: > On Fri, 1 Dec 2023, Jason Merrill wrote: > >> On 12/1/23 12:32, Patrick Palka wrote: >>> On Tue, 14 Nov 2023, Jason Merrill wrote: >>> >>>> On 11/14/23 11:10, Patrick Palka wrote: >>>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for >>>>> trunk? >>>>> >>>>> -- >8 -- >>>>> >>>>> For decltype((x)) within a lambda where x is not captured, we dubiously >>>>> require that the lambda has a capture default, unlike for decltype(x). >>>>> This patch fixes this inconsistency; I couldn't find a justification for >>>>> it in the standard. >>>> >>>> The relevant passage seems to be >>>> >>>> https://eel.is/c++draft/expr.prim#id.unqual-3 >>>> >>>> "If naming the entity from outside of an unevaluated operand within S >>>> would >>>> refer to an entity captured by copy in some intervening lambda-expression, >>>> then let E be the innermost such lambda-expression. >>>> >>>> If there is such a lambda-expression and if P is in E's function parameter >>>> scope but not its parameter-declaration-clause, then the type of the >>>> expression is the type of a class member access expression ([expr.ref]) >>>> naming >>>> the non-static data member that would be declared for such a capture in >>>> the >>>> object parameter ([dcl.fct]) of the function call operator of E." >>>> >>>> In this case I guess there is no such lambda-expression because naming x >>>> won't >>>> refer to a capture by copy if the lambda doesn't capture anything, so we >>>> ignore the lambda. >>>> >>>> Maybe refer to that in a comment? OK with that change. >>>> >>>> I'm surprised that it refers specifically to capture by copy, but I guess >>>> a >>>> capture by reference should have the same decltype as the captured >>>> variable? >>> >>> Ah, seems like it. So maybe we should get rid of the redundant >>> by-reference capture-default handling, to more closely mirror the >>> standard? >>> >>> Also now that r14-6026-g73e2bdbf9bed48 made capture_decltype return >>> NULL_TREE to mean the capture is dependent, it seems we should just >>> inline capture_decltype into finish_decltype_type rather than >>> introducing another special return value to mean "fall back to ordinary >>> handling". >>> >>> How does the following look? Bootstrapped and regtested on >>> x86_64-pc-linux-gnu. >>> >>> -- >8 -- >>> >>> PR c++/83167 >>> >>> gcc/cp/ChangeLog: >>> >>> * semantics.cc (capture_decltype): Inline into its only caller ... >>> (finish_decltype_type): ... here. Update nearby comment to refer >>> to recent standard. Restrict uncaptured variable handling to just >>> lambdas with a by-copy capture-default. >>> >>> gcc/testsuite/ChangeLog: >>> >>> * g++.dg/cpp0x/lambda/lambda-decltype4.C: New test. >>> --- >>> gcc/cp/semantics.cc | 107 +++++++----------- >>> .../g++.dg/cpp0x/lambda/lambda-decltype4.C | 15 +++ >>> 2 files changed, 55 insertions(+), 67 deletions(-) >>> create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C >>> >>> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc >>> index fbbc18336a0..fb4c3992e34 100644 >>> --- a/gcc/cp/semantics.cc >>> +++ b/gcc/cp/semantics.cc >>> @@ -53,7 +53,6 @@ along with GCC; see the file COPYING3. If not see >>> static tree maybe_convert_cond (tree); >>> static tree finalize_nrv_r (tree *, int *, void *); >>> -static tree capture_decltype (tree); >>> /* Used for OpenMP non-static data member privatization. */ >>> @@ -11856,21 +11855,48 @@ finish_decltype_type (tree expr, bool >>> id_expression_or_member_access_p, >>> } >>> else >>> { >>> - /* Within a lambda-expression: >>> - >>> - Every occurrence of decltype((x)) where x is a possibly >>> - parenthesized id-expression that names an entity of >>> - automatic storage duration is treated as if x were >>> - transformed into an access to a corresponding data member >>> - of the closure type that would have been declared if x >>> - were a use of the denoted entity. */ >>> if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr)) >>> && current_function_decl >>> && LAMBDA_FUNCTION_P (current_function_decl)) >>> { >>> - type = capture_decltype (STRIP_REFERENCE_REF (expr)); >>> - if (!type) >>> - goto dependent; >>> + /* [expr.prim.id.unqual]/3: If naming the entity from outside of an >>> + unevaluated operand within S would refer to an entity captured by >>> + copy in some intervening lambda-expression, then let E be the >>> + innermost such lambda-expression. >>> + >>> + If there is such a lambda-expression and if P is in E's function >>> + parameter scope but not its parameter-declaration-clause, then >>> the >>> + type of the expression is the type of a class member access >>> + expression naming the non-static data member that would be >>> declared >>> + for such a capture in the object parameter of the function call >>> + operator of E." */ >> >> Hmm, looks like this code is only checking the innermost lambda, it needs to >> check all containing lambdas for one that would capture it by copy. > > Unfortunately this seems to be a can of worms, since IIUC we also have > to check that there's no non-default-capture lambda in the stack as > well, e.g. > > int main() { > int x; > [] { > [=] { > using ty1 = decltype((x)); // refers to local variable despite > // innermost by-copy capture-default > using ty1 = int&; > }; > }; > [=] { > [] { > using ty1 = decltype((x)); // same > using ty1 = int&; > }; > }; > [=] { > [&] { > using ty1 = decltype((x)); // refers to hypothetical capture proxy > using ty1 = const int&; > }; > }; > [&] { > [=] { > using ty1 = decltype((x)); // same > using ty1 = const int&; > }; > }; > } > > And we have to refine the logic for whether to perform the HIDDEN_LAMBDA > name lookup (which we currently unconditionally do): > > int main() { > int x; > [x] { > [x] { > using ty1 = decltype((x)); // refers to actual capture proxy, > // found by HIDDEN_LAMBDA name lookup > using ty1 = const int&; > }; > }; > [x] { > [] { > using ty1 = decltype((x)); // refers to local variable, > // HIDDEN_LAMBDA name lookup not performed > using ty1 = int&; > }; > }; > } > > These could probably be fixed locally within finish_decltype_type, > but then there's PR86697 which basically extends all of these > capture-related issues to 'decltype(f(x))' instead of 'decltype((x))', > which suggests a proper fix should probably be in process_outer_var_ref > instead of in finish_decltype_type? Perhaps when in an unevaluated > context, process_outer_var_ref should still rewrite uses into capture > proxies but not actually add them to the closure or something like that? Or remove them in prune_lambda_captures if there's no real use? > I don't think I have the cycles to work on these issues this stage.. > Would the latest patch be OK at least? It seems to be a strict > improvement. OK if you open a PR for the other cases and add a FIXME comment referring to it. Jason
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 8090c71809f..6fdd6c45972 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -11732,7 +11732,8 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, /* If the expression is just "this", we want the cv-unqualified pointer for the "this" type. */ type = TYPE_MAIN_VARIANT (TREE_TYPE (expr)); - else + + if (!type) { /* Otherwise, where T is the type of e, if e is an lvalue, decltype(e) is defined as T&; if an xvalue, T&&; otherwise, T. */ @@ -12639,8 +12640,7 @@ capture_decltype (tree decl) switch (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lam)) { case CPLD_NONE: - error ("%qD is not captured", decl); - return error_mark_node; + return NULL_TREE; case CPLD_COPY: type = TREE_TYPE (decl); diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C new file mode 100644 index 00000000000..0062d7b8672 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-decltype4.C @@ -0,0 +1,15 @@ +// PR c++/83167 +// { dg-do compile { target c++11 } } + +int main() { + int x; + const int y = 42; + + [] { + using ty1 = decltype((x)); + using ty1 = int&; + + using ty2 = decltype((y)); + using ty2 = const int&; + }; +}