Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is template function of data member a dependent name only when qualifying with "this"?

struct Bar {
    template<typename>
    void baz() {
    }
};

template<typename>
struct Foo {
    Bar bar;

    Foo() {
        bar.baz<int>();
    }
};

int main() {
    return 0;
}

This code compiles fine (in GCC 4.7), but if I prefix the call to bar.baz<int>() with this->, baz becomes a dependent name that needs disambiguating with template.

bar.baz<int>(); // OK
this->bar.baz<int>(); // error
this->bar.template baz<int>(); // OK

Surely this->bar can only refer to Bar bar, whose member baz is clearly a template? Why does the addition of this-> make this code ambiguous to the compiler?

p.s. Originally, bar was a data member of a base class template which needed disambiguating with this->, but I have simplified the example for the purpose of this question.

like image 224
Joseph Thomson Avatar asked Jul 05 '14 16:07

Joseph Thomson


People also ask

What is the purpose of a template function?

Function templates. Function templates are special functions that can operate with generic types. This allows us to create a function template whose functionality can be adapted to more than one type or class without repeating the entire code for each type. In C++ this can be achieved using template parameters.

What is dependent name?

A dependent name is a name that depends on the type or the value of a template parameter. For example: template<class T> class U : A<T> { typename T::B x; void f(A<T>& y) { *y++; } }; The dependent names in this example are the base class A<T> , the type name T::B , and the variable y .

Can a member function be a template?

Member functions can be function templates in several contexts. All functions of class templates are generic but are not referred to as member templates or member function templates. If these member functions take their own template arguments, they are considered to be member function templates.

What is dependent name in C++?

A dependent name is essentially a name that depends on a template parameter. A dependent name can be a type, a non-type, or a template parameter. To express that a dependent name stands for a type or a template, you have to use the keywords typename or template .


1 Answers

this->bar.baz<int>(); // error

The statement above, within the definition of template<typename T> Foo<T>::Foo(), is well-formed, and should be accepted if C++11 mode or C++1y mode is enabled. But it was technically ill-formed according to C++03.

Both standards agree that this is a type-dependent expression:

C++03 14.6.2.1/1; N3690 14.6.2.1/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,

[T is a dependent type, and so is Foo<T>.]

C++03/N3690 14.6.2.2/2:

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

[Since Foo<T> is a dependent type, the expression this in its member definition is type-dependent.]

Both standards begin 14.6.2.2 with:

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

C++03 has only three simple categories of expressions with more exact descriptions:

  • Primary expressions (this and looked-up names)

  • Expressions that specify their own type (like casts and new-expressions)

  • Expressions with constant type (like literals and sizeof).

The first category is defined in C++03 14.6.2.2/3:

An id-expression is type-dependent if it contains:

  • an identifier that was declared with a dependent type,

  • a template-id that is dependent,

  • a conversion-function-id that specifies a dependent type,

  • a nested-name-specifier that contains a class-name that names a dependent type.

So the lone expression bar is not dependent: it is an identifier and an id-expression, but none of the above apply.

But this->bar is not an id-expression, or in any of the other C++03 exceptions, so we have to follow the subexpression rule. Since subexpression this is type-dependent, the containing expression this->bar is also type-dependent.

But in fact, as you noticed, the type of this->bar can be known while parsing the template definition, without instantiating any template arguments. It is declared as a member of the primary template, so the name must bind to that member declaration. A template specialization might make Foo<T>::bar undeclared or declared in a different way, but in that case the primary template won't be used at all and the current definition of Foo() is ignored for that specialization. Which is why C++11 defined the concept of "the current instantiation" and used it for a further exception to the contagiousness of type-dependent expressions.

N3690 14.6.2.1/1:

A name refers to the current instantiation if it is

  • in the definition of a class template, a nested class of a class template, a member of a class template, or a member of a nested class of a class template, the injected-class-name of the class template or nested class

  • in the definition of a primary class template or a member of a primary class template, the name of the class template followed by the template argument list of the primary template (as described below) enclosed in <> (or an equivalent template alias specialization),

  • ...

[The first bullet says Foo is the current instantiation. The second says Foo<T> is the current instantiation. In this example, both name the same type.]

14.6.2.1/4:

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

  • An unqualified name that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof.

  • A qualified-id in which ...

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

[The first bullet says bar alone is a member of the current instantiation. The third bullet says this->bar is a member of the current instantiation.]

Finally, C++11 adds a fourth category of rules for type-dependent expressions, for member access. 14.6.2.2/5:

A class member access expression 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.

this->bar does refer to a member of the current instantiation, but the type Bar of the referenced member is not dependent. So now this->bar is not type-dependent, and the name baz in this->bar.baz is looked up during the template definition as a non-dependent name. The template keyword is not needed before baz.

like image 163
aschepler Avatar answered Sep 17 '22 12:09

aschepler