Search code examples
c++templateslanguage-lawyerdependent-typecompiler-bug

Can a throw or delete expression ever be dependent?


Both gcc 5.0 and clang 3.6 require the typename keyword in the following example:

template<typename T>
struct B
{
    typedef int Type;
};

template<int n>
struct A
{
    typedef typename B<decltype(throw (int*)n)>::Type Throw;
    typedef typename B<decltype(delete (int*)n)>::Type Delete;
};

This is covered by the following wording in the C++11 standard:

[except]/2

A throw-expression is of type void.

[expr.delete]/1

The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function to a pointer to object type. The result has type void.

So I'm assuming decltype produces void in both cases.

[expr.const]/2

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression

  • a new-expression

  • a throw-expression

This suggests that an expression involving either throw or delete cannot be a constant expression.

[temp.dep.type]/8

A type is dependent if it is

  • a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent

  • denoted by decltype(expression), where expression is type-dependent

So B<decltype(..)> is dependent only if the expression is type-dependent.

[temp.dep.expr]/4

Expressions of the following forms are never type-dependent (because the type of the expression cannot be dependent):

delete cast-expression
throw assignment-expression

This suggests that neither expression can be type-dependent.

Are gcc and clang both wrong?


Solution

  • Let's go back to when typename is required. §14.6 [temp.res]/p3, all quotes are from N4140:

    When a qualified-id is intended to refer to a type that is not a member of the current instantiation (14.6.2.1) and its nested-name-specifier refers to a dependent type, it shall be prefixed by the keyword typename, forming a typename-specifier.

    The qualified-id in this case is B<decltype(throw (int*)n)>::Type (and the delete version, for which the analysis is exactly the same). So typename is required if the nested-name-specifier, or B<decltype(throw (int*)n)>::, refers to a dependent type.

    §14.6.2.1 [temp.dep.type]/p8 says, with six unrelated bullets omitted, that

    A type is dependent if it is

    [...]

    (8.7) — a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent, or

    (8.8) — denoted by decltype(expression), where expression is type-dependent (14.6.2.2).

    B<decltype(throw (int*)n)> is a simple-template-id. The template name, B, is not a template parameter. The only template argument, decltype(throw (int*)n), is not an expression, so B<decltype(throw (int*)n)> is dependent only if decltype(throw (int*)n) is a dependent type. decltype(throw (int*)n), in turn, per bullet 8.8, is only dependent if throw (int*)n is type-dependent. But we know that, per §14.6.2.2 [temp.dep.expr]/p4:

    Expressions of the following forms are never type-dependent (because the type of the expression cannot be dependent):

    [...]

    ::opt delete cast-expression

    [...]

    throw assignment-expressionopt

    [...]

    Therefore, throw (int*)n is not type-dependent, and so decltype(throw (int*)n) is not a dependent type, and so B<decltype(throw (int*)n)> is not a dependent type, and so typename is not required for B<decltype(throw (int*)n)>::Type, and so yes, this is a compiler bug.