Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the name of a non-static-member dependent when used within a non-static member function?

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

template<int n>
struct I
{
    typedef int Type;
};

template<typename T>
struct A
{
    int m;

    void f()
    {
        typedef typename I<sizeof m>::Type Type; // typename required
    }
};

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

[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

So I<sizeof m> is dependent if sizeof m is value-dependent.

[temp.dep.expr]/4

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

sizeof unary-expression

[temp.dep.constexpr]/2

Expressions of the following form are value-dependent if the unary-expression or expression is typedependent or the type-id is dependent:

sizeof unary-expression

So sizeof m is only dependent if m is dependent.

[expr.prim.general]/8

Within the definition of a non-static member function, an identifier that names a non-static member is transformed to a class member access expression

So m is the member in a class member access expression.

[temp.dep.type]/4

A name is a member of the current instantiation if it is

  • An id-expression denoting the member in a class member access expression (5.2.5) for which the type of the object expression is the current instantiation, and the id-expression, when looked up (3.4.5), refers to at least one member of the current instantiation or a non-dependent base class thereof.

So it seems that m is a member of the current instantiation.

[temp.dep.type]/5

A name is a member of an unknown specialization if it is

  • An id-expression denoting the member in a class member access expression (5.2.5) in which either

    • the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the id-expression does not find a member of the current instantiation or a non-dependent base class thereof; or

    • the type of the object expression is dependent and is not the current instantiation.

So m is NOT a member of an unknown specialization - it would be found by name lookup to be a member of the current instantiation.

[temp.dep.expr]/3

An id-expression is type-dependent if it contains

  • an identifier associated by name lookup with one or more declarations declared with a dependent type,
  • a nested-name-specifier or a qualified-id that names a member of an unknown specialization

Since m is of type int and is not a member of an unknown specialization, neither of these bullets would make the id-expression m dependent.

[temp.dep.expr]/5

A class member access expression (5.2.5) is type-dependent if the expression refers to a member of the current instantiation and the type of the referenced member is dependent, or the class member access expression refers to a member of an unknown specialization.

When m is transformed to a class member access expression, it is still not dependent because it does not refer to a member of an unknown specialization.

Should m be treated as dependent? On a related note, should this->m be treated as dependent? What about std::declval<A>().m ?

EDIT

And finally, should &A::m be dependent?

like image 575
willj Avatar asked Dec 31 '14 14:12

willj


2 Answers

As correctly stated by you, sizeof m is transformed into sizeof (*this).m.
sizeof is dependent only if the argument expression is type-dependent, which it isn't, according to [temp.dep.expr]/5:

A class member access expression (5.2.5) is type-dependent if the expression refers to a member of the current instantiation and the type of the referenced member is dependent, or the class member access expression refers to a member of an unknown specialization.

The type of m is not dependent, and the expression does not refer to a member of an unknown specialization either - [temp.dep.type]/6:

A name is a member of an unknown specialization if it is

  • An id-expression denoting the member in a class member access expression (5.2.5) in which either
    • the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the id-expression does not find a member of a class that is the current instantiation or a non-dependent base class thereof; or
    • the type of the object expression is dependent and is not the current instantiation.

Even though the type of (*this) is dependent, it is the current instantiation. And name lookup should find m to be a member of the current instantiation.

So *this isn't type-dependent and thus sizeof (*this).m isn't dependent. (sizeof m is also not dependent outside any non-static data member initializer of function definition, which I accidentally covered in my second, deleted answer).


For sizeof std::declval<A>().m to be dependent, std::declval<A>().m must be type-dependent.
std::declval<A>().m seems to be type-dependent, but I'm not certain. As specified in [temp.dep.expr]/5 which I quoted above, the only possibility is that m in the expression is a member of an unknown specialization, which we have to show it is.

A name is a member of an unknown specialization if it is

  • An id-expression denoting the member in a class member access expression (5.2.5) in which either
    • the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the id-expression does not find a member of a class that is the current instantiation or a non-dependent base class thereof; or
    • the type of the object expression is dependent and is not the current instantiation.

Here are the facts:

  • The object expression std::declval<A>() is type-dependent.

  • The lookup of std::declval<A> is only done in the definition context since it is a qualified-id, which are never dependent names ([temp.dep]/1).

There is exactly one declval function template found by qualified name lookup, but we cannot know if the return type of that candidate is the current instantiation at definition time. Here in particular, add_rvalue_reference might have specializations not known at definition time (A scenario similar to this one). So because we do not know whether std::declval<A>() is the current instantiation, (we assume) it isn't, which makes the whole expression type-dependent.


&A::m is of the form &qualified-id, which is covered by [temp.dep.constexpr]/5:

An expression of the form &qualified-id where the qualified-id names a dependent member of the current instantiation is value-dependent.

[temp.dep.type]/5:

A name is a dependent member of the current instantiation if it is a member of the current instantiation that, when looked up, refers to at least one member of a class that is the current instantiation.

Clearly A::m is a member of the current instantiation, thus &A::m is value-dependent.
Also &A::m is type-dependent: The subexpression A is equivalent to A<T> according to [temp.local], which is a simple-template-id with dependent template arguments.

like image 145
Columbo Avatar answered Nov 17 '22 22:11

Columbo


The answer hinges on whether m can be looked up to determine that it is a member of the current instantiation. The id-expression m is transformed to a class member access (*this).m, meaning that the rules for qualified name lookup within a class member access are applicable.

In general, the type of a type-dependent expression cannot be determined. It's not completely clear whether an exception should be made for (*this). and this->. An expression containing this is type-dependent, but both (*this). and this-> unambiguously name the current instantiation.

m

The expression m is indeed not type-dependent, because it refers to a member of the current instantiation.

In the context of a non-static member, m is transformed into the class member access expression (*this).m.

[class.mfct.non-static]/3

When an id-expression (5.1) that is not part of a class member access syntax (5.2.5) and not used to form a pointer to member (5.3.1) is used in a member of class X in a context where this can be used (5.1.1), if name lookup (3.4) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (5.2.5) using (*this)

The transformation occurs because m is a member of class A used within a non-static-member of class A.

[expr.prim.general]/3

If a declaration declares a member function or member function template of a class X, the expressionthis` is a prvalue of type “pointer to cv-qualifier-seq X” between the optional cv-qualifer-seq and the end of the function-definition, member-declarator, or declarator. It shall not appear before the optional cv-qualifier-seq and it shall not appear within the declaration of a static member function (although its type and value category are defined within a static member function as they are within a non-static member function).

[expr.prim.general]/5

The expression this shall not appear in any other context. [ Example:

class Outer {
    int a[sizeof(*this)]; // error: not inside a member function
    unsigned int sz = sizeof(*this); // OK: in brace-or-equal-initializer

    void f() {
        int b[sizeof(*this)]; // OK

        struct Inner {
            int c[sizeof(*this)]; // error: not inside a member function of Inner
        };
    }
};

—end example ]

The above example explicitly permits use of this within a sizeof expression within a non-static member.

[temp.dep.expr]/2

this is type-dependent if the class type of the enclosing member function is dependent

Therefore this is type-dependent within the definition of a member function of a class template.

[temp.dep.expr]/1

Except as described below, an expression is type-dependent if any subexpression is type-dependent.

However, the above is overruled by the exception in [temp.dep.expr]/5 quoted in the question.

this->m

The expression this->m is also not type-dependent, because it is also a class member access expression which refers to a member of the current instantiation.

std::declval<A>().m

The expression std::declval<A>().m has to be type-dependent, as the return type of std::declval<A>() could depend on the type of A.

[temp.local]/1

Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injectedclass- name can be used as a template-name or a type-name. When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-typespecifier of a friend class template declaration, it refers to the class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>

Therefore A is transformed to A<T>.

[temp.dep.type]/8

A type is dependent if it is

  • a template parameter,

  • 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

This confirms that A<T> is a dependent type, meaning that A is also dependent.

[temp.dep.expr]/3

An id-expression is type-dependent if it contains

  • a template-id that is dependent,

So std::declval<A> is a type-dependent expression. It follows that std::declval<A>().m is type-dependent because it contains the type-dependent subexpression std::declval<A>.

&A::m

The expression &A::m logically has to be type-dependent because it has the type int A<T>::* which is a dependent type.

The expression &A::m is transformed to &A<T>::m because A is the injected class name - as shown above.

The id-expression A<T>::m is type-dependent according to [temp.dep.expr]/3 because it contains a template-id A<T> that is dependent. Therefore the expression &A<T>::m is type-dependent according to [temp.dep.expr]/1.

A::m

The expression A::m is transformed to A<T>::m because A is the injected class name - as shown above. The expression A<T>::m is further transformed to (*this).A<T>::m because A<T>::m names a non-static member of A.

The id-expression A<T>::m is type-dependent according to [temp.dep.expr]/3 because it contains a template-id A<T> that is dependent. The class member access expression (*this).A<T>::m refers to a member of the current instantiation, so [temp.dep.expr]/5 applies but does not make the expression type-dependent - nor does it contradict [temp.dep.expr]/3.

Defect?

Given the interpretation above, an id-expression naming a member of A qualified with A or A<T> would become type-dependent, which seems unnecessary and inconsistent. Consider that a type-name qualified with A or A<T> would not be dependent in the same context.

If the intention of [temp.dep.expr]/5 is that (*this).m is not type-dependent, then it follows that (*this).A<T>::m should also not be type-dependent. On the other hand, the intention could be that the name of a non-static member is always dependent. I've posted a question to std-discussion to clarify this point: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/gEvZ7mmXEC8

like image 1
willj Avatar answered Nov 17 '22 21:11

willj