2
votes

Another one for the language lawyers.

For the following code, regardless of whether the explicit instantiation of A<int> is present:

clang 3.0 requires typename in the declaration of Type1 and Type3, but not of Type2.

gcc 4.8.1 requires typename in the declaration of Type1, but not of Type2 or Type3.

struct C
{
    int f1();
    static int f2();
    int m;
};

template<int i>
struct S
{
    typedef int Type;
};

template<typename T>
struct B : C
{
};

template<typename T>
struct A : B<T>
{
    void f()
    {
        typedef typename S<sizeof(C::f1())>::Type Type1; // typename required
        typedef S<sizeof(C::f2())>::Type Type2; // typename not required
        typedef typename S<sizeof(C::m)>::Type Type3; // typename not required?
    }
};

template struct A<int>;

I figure typename is required in the declaration of Type1 because the implied object argument for a call to a nonstatic member is (*this), the type of which is dependent. See [over.call.func]:

In unqualified function calls, the name is not qualified by an -> or . operator and has the more general form of a primary-expression. The name is looked up in the context of the function call following the normal rules for name lookup in function calls. The function declarations found by that lookup constitute the set of candidate functions. Because of the rules for name lookup, the set of candidate functions consists (1) entirely of non-member functions or (2) entirely of member functions of some class T. In case (1), the argument list is the same as the expression-list in the call. In case (2), the argument list is the expression-list in the call augmented by the addition of an implied object argument as in a qualified function call. If the keyword this is in scope and refers to class T, or a derived class of T, then the implied object argument is (*this).

Following this logic, typename is not required in the declaration of Type2 because the member is static. Nor is typename required in the declaration of Type3 because the expression is not a call to a nonstatic member.

I've read through this: Where and why do I have to put the "template" and "typename" keywords? .. and through the rules in [temp.dep.type] and [temp.dep.expr]. I can't see anything that specifies whether a nonstatic member function name should be given special treatment when determining if an expression is dependent. Does the standard specify this?

EDIT: removed discussion of transformation into class member access expressions based on [class.mfct.non-static] - Casey's answer discusses this in much greater detail.

2
"an id-expression which names a non-static member (of any class) would be invalid unless it referred to a member of a base-class" is not true, such an id-expression can be used to form a pointer-to-member of a foreign class, e.g., &C::f1 would be a valid expression.Casey

2 Answers

3
votes

C is a non-dependent name resolved during phase 1 lookup through unqualified name lookup. C::f1 and C::f2 are non-dependent names resolved through qualified name lookup also in the first phase of template lookup. This is true whether or not C is a base of A.

The text of [class.mfct.non-static] quoted in the question seems to be from C++03. The C++11 text (9.3.1/3) reads:

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 [emph. mine] 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) (9.3.2) as the postfix-expression to the left of the . operator.

C::f1 is potentially evaluated in a context where this can be used, so C::f1 is transformed into (*this).C::f1. Since B<T> explicitly depends on a template parameter, (*this).C::f1 is a member of an unknown specialization per 14.6.2.1/5. [The standard uses this term "member of an unknown specialization" to indicate expressions that could indicate members for some specialization of the template but are not provably so for all specializations. ]

Since (*this).C::f1 denotes a member of an unknown specialization, it is a type-dependent class member access expression per 14.6.2.2/5. By extension, (*this).C::f1() is type-dependent per 14.6.2.2/1. sizeof((*this).C::f1()) is then value-dependent per 14.6.2.3/2. S<sizeof((*this).C::f1())> - a simple-template-id with a value-dependent argument expression - is then a dependent type per 14.6.2.1/8.

Since S<sizeof((*this).C::f1())> is dependent,

S<sizeof((*this).C::f1())>::Type

is dependent per 14.6.2/1 "...if the unqualified-id of the id-expression is a template-id in which any of the template arguments depends on a template parameter." And last - but not least, 14.6/2 requires the keyword typename be used to indicate that a dependent name refers to a type:

typename S<sizeof((*this).C::f1())>::Type

I think that's the full chain of reasoning.

EDIT: This answer appears to be wrong. The phrase "potentially evaluated" is defined in 3.2/2 One Definition Rule [basic.def.odr]:

An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof.

C::f1 in the example is certainly a subexpression of C::f1() which is an unevaluated operand of sizeof per 5.3.3/1 Sizeof [expr.sizeof].

EDIT: I believe the expression qualified-id() is currently not but should be considered type-dependent when (1) it appears inside a member function of a class template and (2) the qualified-id is a non-dependent name that denotes an overload set of non-static member functions of some class and (3) the type of this is dependent. Determining the type of the expression requires overload resolution per 13.3.1.1.1/3 which is impossible when this is in scope without determining the type of the class to which this refers.

I think this could be achieved by adding a bullet to 14.6.2.1/5 [temp.dep.type] that reads:

  • A qualified-id used in an unqualified function call (13.3.1.1.1/3 [over.call.func]) that denotes a set of member functions of some class T that is not the current instantiation or a non-dependent base class, when the current instantiation has at least one dependent base class.
2
votes

I'm not totally sure, but here's an explanation that uses your quote:

  • C::f2 is just a plain function, so it is known unconditionally, and sizeof(C::f2()) does not depend on the template parameter T -- it's just a plain type, like S<6>.

  • C::f1() is actually this->f1(), and since this depends on the template parameter T, S<sizeof(this->f1())> is a dependent type, and thus needs disambiguation.