[llvm-bugs] [Bug 38398] New: Missing const when denoting type of name of copy-captured entity in unevaluated compound expression

via llvm-bugs llvm-bugs at lists.llvm.org
Tue Jul 31 18:37:57 PDT 2018


https://bugs.llvm.org/show_bug.cgi?id=38398

            Bug ID: 38398
           Summary: Missing const when denoting type of name of
                    copy-captured entity in unevaluated compound
                    expression
           Product: clang
           Version: trunk
          Hardware: PC
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P
         Component: C++
          Assignee: unassignedclangbugs at nondot.org
          Reporter: eracpp at eml.cc
                CC: dgregor at apple.com, llvm-bugs at lists.llvm.org

There are certain cases when Clang fails to apply const qualifiers when
denoting the type of a copy-captured entity within a lambda expression with no
`mutable` specifier.

Consider the following (https://godbolt.org/g/Tne4Qa):

----
template <typename T> T f(T&& x);

int main() {
  int _x = 0;
  int& x = _x;

  [=]{
    decltype((x))         v = x; // OK
    decltype(f(x))        v = x; // !!
    decltype((void(), x)) v = x; // !!
  }();
}
----

The type of the expression `x` in these decltype specifiers should be that of
a class member access expression, as specified in [expr.prim.id.unqual]-2:
  If the entity is a local entity and naming it from outside of an unevaluated
  operand within the declarative region where the unqualified-id appears would
  result in some intervening lambda-expression capturing it by copy, 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
  closure object of the innermost such intervening lambda-expression.

As the lambda expression does not have a `mutable` specifier, the lambda
expression's `operator()` is a const member function. Therefore, the types
denoted should all be `int const&`, as specified by [dcl.type.simple]-4.5:
  For an expression e, the type denoted by decltype(e) is defined as follows:
  (...) - ...
  (4.3) - otherwise, if e is an unparenthesized id-expression or an
          unparenthesized class member access, decltype(e) is the type of the
          entity named by e. If there is no such entity, or if e names a set of
          overloaded functions, the program is ill-formed;
  (...) - ...
  (4.5) - otherwise, if e is an lvalue, decltype(e) is T&, where T is the type
          of e;

However, if we examine the AST generated for each of the decltype specifiers by
the following code (note that the type of `x` has been changed to `int&&` to
help differentiate between the type of the entity`int&&` and the type of the
class member access expressions, both correct `int const&` and incorrect
`int&`):

----
template <typename T> T f(T&& x);

int main() {
  int&& x = 0;

  [=]{
    using A = decltype(x);
    using B = decltype((x));
    using C = decltype(f(x));
    using D = decltype((void(), x))
  }();
}
----

We can see that the `QualType 'const int' const` child node for
`LValueReferenceType` present for B is missing in the case of C and D
(https://godbolt.org/g/G2wtQE):

DeclStmt <line:7:5, col:26>
`-TypeAliasDecl <col:5, col:15> col:11 A 'decltype(x)':'int &&'
  `-DecltypeType 'decltype(x)' sugar
    |-DeclRefExpr <col:24> 'int' lvalue Var 0x56032ea514f0 'x' 'int &&'
    `-RValueReferenceType 'int &&'
      `-BuiltinType 'int'
DeclStmt <line:8:5, col:28>
`-TypeAliasDecl <col:5, col:15> col:11 B 'decltype((x))':'const int &'
  `-DecltypeType 'decltype((x))' sugar
    |-ParenExpr <col:24, col:26> 'int' lvalue
    | `-DeclRefExpr <col:25> 'int' lvalue Var 0x56032ea514f0 'x' 'int &&'
    `-LValueReferenceType 'const int &'
      `-QualType 'const int' const
        `-BuiltinType 'int'
DeclStmt <line:9:5, col:29>
`-TypeAliasDecl <col:5, col:15> col:11 C 'decltype(f(x))':'int &'
  `-DecltypeType 'decltype(f(x))' sugar
    |-CallExpr <col:24, col:27> 'int' lvalue
    | |-ImplicitCastExpr <col:24> 'int &(*)(int &)' <FunctionToPointerDecay>
    | | `-DeclRefExpr <col:24> 'int &(int &)' lvalue Function 0x56032ea7bf40
    | |   'f' 'int &(int &)' (FunctionTemplate 0x56032ea512e0 'f')
    | `-DeclRefExpr <col:26> 'int' lvalue Var 0x56032ea514f0 'x' 'int &&'
    `-LValueReferenceType 'int &'
      `-BuiltinType 'int'
DeclStmt <line:10:5, col:36>
`-TypeAliasDecl <col:5, col:15> col:11 D 'decltype((void() , x))':'int &'
  `-DecltypeType 'decltype((void() , x))' sugar
    |-ParenExpr <col:24, col:34> 'int' lvalue
    | `-BinaryOperator <col:25, col:33> 'int' lvalue ','
    |   |-CXXScalarValueInitExpr <col:25, col:30> 'void'
    |   `-DeclRefExpr <col:33> 'int' lvalue Var 0x56032ea514f0 'x' 'int &&'
    `-LValueReferenceType 'int &'
      `-BuiltinType 'int'

Clang incorrectly denotes the type of the expression `x` in case C and D as
`int&`. It correctly identifies the expression as a class member access
expression (otherwise it would denoted the type as `int&&` as it did in case A,
according to [expr.prim.lambda.capture]-11 and [dcl.type.simple]-4.3), but
fails to correctly apply [expr.ref]-4.2:
  If E2 is declared to have type “reference to T”, then E1.E2 is an lvalue; the
  type of E1.E2 is T. Otherwise, one of the following rules applies.
  - If E2 is a non-static data member and the type of E1 is “cq1 vq1 X”, and
    the type of E2 is “cq2 vq2 T”, the expression designates the named member
    of the object designated by the first expression. If E1 is an lvalue, then
    E1.E2 is an lvalue; otherwise E1.E2 is an xvalue. Let the notation vq12
    stand for the “union” of vq1 and vq2; that is, if vq1 or vq2 is volatile,
    then vq12 is volatile. Similarly, let the notation cq12 stand for the
    “union” of cq1 and cq2; that is, if cq1 or cq2 is const, then cq12 is
    const. If E2 is declared to be a mutable member, then the type of E1.E2 is
    “vq12 T”. If E2 is not declared to be a mutable member, then the type of
    E1.E2 is “cq12 vq12 T”.

Stranger still, the use of `decltype(auto)` seems to apply the correct
behavior, even when the semantics of `decltype(auto) v = e;` should be
identical to `decltype(e) v = e;`, according to [dcl.type.auto.deduct]:
  Placeholder type deduction is the process by which a type containing a
  placeholder type is replaced by a deduced type. A type T containing a
  placeholder type, and a corresponding initializer e, are determined as
  follows: ... If the placeholder is the decltype(auto) type-specifier, T shall
  be the placeholder alone. The type deduced for T is determined as described
  in [dcl.type.simple], as though e had been the operand of the decltype.

Here is a comprehensive test case (https://godbolt.org/g/wDwMNS):

template <typename, typename> constexpr bool same = false;
template <typename T> constexpr bool same<T, T> = true;

template <typename T> T f(T&& t) { return static_cast<T&&>(t); }

int main() {
  int&& x = 0;

  [=]{
    using X = decltype(x);
    using Y = decltype((x));
    using Z = decltype(f(x));
    using T = decltype((void(), x));

    static_assert(same<X, int&&>, "");
    static_assert(same<Y, int const&>, "");
    static_assert(same<Z, int const&>, ""); // !!
    static_assert(same<T, int const&>, ""); // !!

    decltype(auto) a = (x);
    decltype(auto) b = f(x);
    decltype(auto) c = (void(), x);

    using A = decltype(a);
    using B = decltype(b);
    using C = decltype(c);

    static_assert(same<A, int const&>, "");
    static_assert(same<B, int const&>, ""); // OK?
    static_assert(same<C, int const&>, ""); // OK?
  }();
}

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20180801/cf0d3b56/attachment.html>


More information about the llvm-bugs mailing list