This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 117a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2025-06-16


2228. Ambiguity resolution for cast to function type

Section: 9.3.3  [dcl.ambig.res]     Status: review     Submitter: Richard Smith     Date: 2016-02-02     Liaison: EWG

Consider:

  int x = (int()) + 5; 

This is ill-formed, because 9.3.3 [dcl.ambig.res] paragraph 2 specifies:

An ambiguity can arise from the similarity between a function-style cast and a type-id. The resolution is that any construct that could possibly be a type-id in its syntactic context shall be considered a type-id.

and thus int() is interpreted as a type-id instead of as a function-style cast, so this is an ill-formed cast to a function type.

This seems to be the wrong disambiguation for all cases where there is a choice between a C-style cast and a parenthesized expression: in all those cases, the C-style cast interpretation results in a cast to a function type, which is always ill-formed.

Further, there is implementation divergence in the handling of this example:

  struct T { int operator++(int); T operator[](int); };
  int a = (T()[3])++; // not a cast

EWG 2022-11-11

This is tracked in github issue cplusplus/papers#1376.

Additional notes (May, 2025)

Also consider this example:

  (S())[]->A<int>;              // OK, constructor call
  (S())[]->A<int> {return {};}; // error: C-style cast of lambda

Without the suggested simple rule that (T()) is never a conversion to a function type (which is always semantically ill-formed), syntactic disambiguation would require analysis whether the thing after the arrow is a type (i.e. a trailing return type) or an expression (for operator->).

Suggested resolution (2019-01-02):

  1. Change in 7.6.2 [expr.unary] paragraph 1 as follows:

    unary-expression :
      postfix-expression
      unary-operator cast-expression
      ++ cast-expression
      -- cast-expression
      await-expression
      sizeof unary-expression
      sizeof ( nofun-type-id )
      sizeof ... ( identifier )
      alignof ( nofun-type-id )
      noexcept-expression
      new-expression
      delete-expression
    
  2. Change in 7.6.2.5 [expr.sizeof] paragraph 1 as follows:

    The operand is either an expression, which is an unevaluated operand (7.2.3 [expr.context]), or a parenthesized nofun-type-id.
  3. Change in 7.6.2.6 [expr.alignof] paragraph 1 as follows:

    The operand shall be a nofun-type-id representing a complete object type, or an array thereof, or a reference to one of those types.
  4. Replace type-id with nofun-type-id throughout 7.6.2.8 [expr.new], for example in paragraph 1:

      new-expression :
        ::opt new new-placementopt new-type-id new-initializeropt
        ::opt new new-placementopt ( nofun-type-id ) new-initializeropt
    
  5. Change in 7.6.3 [expr.cast] paragraph 2 as follows:

      cast-expression :
        unary-expression
        ( nofun-type-id ) cast-expression
    
  6. Change in 9.2.9.7.1 [dcl.spec.auto.general] paragraph 6 as follows:

    A placeholder type can also be used in the type-specifier-seq of the new-type-id or in the nofun-type-id of a new-expression (7.6.2.8 [expr.new]). In such a nofun-type-id, the placeholder type shall appear as one of the type-specifiers in the type-specifier-seq or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a type-specifier .
  7. Change in 9.2.9.7.2 [dcl.type.auto.deduct] paragraph 2 as follows:

    A placeholder for a deduced class type can also be used in the type-specifier-seq in the new-type-id or nofun-type-id of a new-expression (7.6.2.8 [expr.new]), as the simple-type-specifier in an explicit type conversion (functional notation) (7.6.1.4 [expr.type.conv]), or as the type-specifier in the parameter-declaration of a template-parameter (13.2 [temp.param]). A placeholder for a deduced class type shall not appear in any other context.
  8. Change in 9.3.1 [dcl.decl.general] paragraph 6 as follows, to fix auto (*p)() -> int(X()); (now a function pointer initialized by X()):

      trailing-return-type :
        -> nofun-type-id
    
  9. Change in 9.3.2 [dcl.name] paragraph 1 as follows:

      nofun-type-id:
        type-specifier-seq nofun-declaratoropt
    
      nofun-declarator:
        ptr-nofun-declarator
        noptr-nofun-declarator parameters-and-qualifiers trailing-return-type
    
      ptr-nofun-declarator:
        noptr-nofun-declarator
        ptr-operator ptr-nofun-declaratoropt
    
      noptr-nofun-declarator:
        noptr-nofun-declarator parameters-and-qualifiers
        noptr-nofun-declaratoropt [ constant-expressionopt ] attribute-specifier-seqopt
        ( ptr-nofun-declarator )
    

    It is possible to identify uniquely the location in the abstract-declarator or nofun-declarator where the identifier would appear if the construction were a declarator in a declaration. The named type is then the same as the type of the hypothetical identifier.

  10. Change in 9.3.3 [dcl.ambig.res] paragraph 2 as follows:

    An ambiguity can arise from the similarity between a function-style cast and a type-id. The resolution is that any construct that could possibly be a type-id in its syntactic context shall be considered a type-id. [ Note: No such ambiguity can arise between an expression and a nofun-type-id. -- end note ] However, a construct that can syntactically be a type-id whose outermost abstract-declarator would match the grammar of an abstract-declarator with a trailing-return-type is considered a type-id only if it starts with auto. [Example 2 :
      template <class T> struct X {};
      template <int N> struct Y {};
      X<int()> a;   // type-id
      X<int(1)> b;  // expression (ill-formed)
      Y<int()> c;   // type-id (ill-formed)
      Y<int(1)> d;  // expression
      void foo(signed char a) {
        sizeof(int());    // type-id (ill-formed) expression
        sizeof(int(a));   // expression
        sizeof(int(unsigned(a)));    // type-id (ill-formed) expression
        (int())+1;              // type-id (ill-formed) expression
        (int(a))+1;             // expression
        (int(unsigned(a)))+1;   // type-id (ill-formed) expression
      }
      typedef struct BB { int C[2]; } *B, C;
      void g() {
      sizeof(B()->C[1]);     // OK, sizeof(expression)
      sizeof(auto()->C[1]);  // error: sizeof of a function returning an array
    }
    
    -- end example ]
  11. Change in 9.13.1 [dcl.attr.grammar] paragraph 1 as follows:

    alignment-specifier :
      alignas ( nofun-type-id ...opt )
      alignas ( constant-expression ...opt )
    
  12. Change in 9.13.2 [dcl.align] paragraph 3 as follows:

    An alignment-specifier of the form alignas( nofun-type-id ) has the same effect as alignas(alignof( nofun-type-id )) (7.6.2.6 [expr.alignof]).
  13. Do not change 13.8.1 [temp.res.general] paragraph 4.

  14. Change in 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:

    Expressions of the following forms are type-dependent only if the type specified by the type-id, nofun-type-id, simple-type-specifier, typename-specifier, or new-type-id is dependent, even if any subexpression is type-dependent:
      simple-type-specifier ( expression-listopt )
      simple-type-specifier braced-init-list
      typename-specifier ( expression-listopt )
      typename-specifier braced-init-list
      ::opt new new-placementopt new-type-id new-initializeropt
      ::opt new new-placementopt ( nofun-type-id ) new-initializeropt
      dynamic_cast < type-id > ( expression )
      static_cast < type-id > ( expression )
      const_cast < type-id > ( expression )
      reinterpret_cast < type-id > ( expression )
      ( nofun-type-id ) cast-expression
    
  15. Change in 13.8.3.3 [temp.dep.expr] paragraph 4 as follows:

      literal
      sizeof unary-expression
      sizeof ( nofun-type-id )
      sizeof ... ( identifier )
      alignof ( nofun-type-id )
      typeid ( expression )
      typeid ( type-id )
      ::opt delete cast-expression
      ::opt delete [ ] cast-expression
      throw assignment-expressionopt
      noexcept ( expression )
      requires-expression
    
  16. Change in 13.8.3.4 [temp.dep.constexpr] paragraph 2 as follows:

    Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id or nofun-type-id is dependent:
      sizeof unary-expression
      sizeof ( nofun-type-id )
      typeid ( expression )
      typeid ( type-id )
      alignof ( nofun-type-id )
    
  17. Change in 13.8.3.4 [temp.dep.constexpr] paragraph 3 as follows:

    Expressions of the following form are value-dependent if either the type-id, nofun-type-id, simple-type-specifier, or typename-specifier is dependent or the expression or cast-expression is value-dependent or any expression in the expression-list is value-dependent or any assignment-expression in the braced-init-list is value-dependent:
      simple-type-specifier ( expression-listopt )
      typename-specifier ( expression-listopt )
      simple-type-specifier braced-init-list
      typename-specifier braced-init-list
      static_cast < type-id > ( expression )
      const_cast < type-id > ( expression )
      reinterpret_cast < type-id > ( expression )
      dynamic_cast < type-id > ( expression )
      ( nofun-type-id ) cast-expression
    

EWG 2025-06-16

EWG agrees with the direction represented by the wording for the May, 2025 notes, above.