In a templated derived class, why do I need to qualify base class member names with "this->" inside a member function?
Asked Answered
D

2

43

While I investigate source code of Qt I saw that trolltech guys explicitly use this keyword to access a field on destructor.

inline ~QScopedPointer()
{
    T *oldD = this->d;
    Cleanup::cleanup(oldD);
    this->d = 0;
}

So, what's the point of this usage? Are there any benefits?

Edit: For those who vote for closing this question, I suspect that this usage is for some class inheritance cases

A part of QScopedPointer class definition:

template <typename T, typename Cleanup = QScopedPointerDeleter<T> >
class QScopedPointer
Damsel answered 26/10, 2011 at 20:7 Comment(0)
P
59

C++ answer (general answer)

Consider a template class Derived with a template base class:

template <typename T>
class Base {
public:
    int d;
};

template <typename T>
class Derived : public Base<T> {
    void f () {
        this->d = 0;
    }
};

this has type Derived<T>, a type which depends on T. So this has a dependent type. So this->d makes d a dependent name. Dependent names are looked-up in the context of the template definition as non-dependent names and in the context of instantiation.

Without this->, the name d would only be looked-up as a non-dependent name, and not be found.

Another solution is to declare d in the template definition itself:

template <typename T>
class Derived : public Base<T> {
    using Base::d;
    void f () {
        d = 0;
    }
};

Qanswer (specific answer)

d is a member of QScopedPointer. It isn't an inherited member. this-> is not necessary here.

OTOH, QScopedArrayPointer is a template class and d is an inherited member of a template base class:

template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> >
class QScopedArrayPointer : public QScopedPointer<T, Cleanup>

so this-> is necessary here:

inline T &operator[](int i)
{
    return this->d[i];
}

It's easy to see that it's easier to just put this-> everywhere.

Understand the reason

I guess it isn't clear to all C++ users why names are looked-up in non-dependent base classes but not in dependent base classes:

class Base0 {
public:
    int nd;
};

template <typename T>
class Derived2 : 
        public Base0, // non-dependent base
        public Base<T> { // dependent base
    void f () {
        nd; // Base0::b
        d; // lookup of "d" finds nothing

        f (this); // lookup of "f" finds nothing
                  // will find "f" later
    }
};

There is a reason beside "the standard says so": cause of way name binding in templates works.

Templates can have name that are bound late, when the template is instantiated: for example f in f (this). At the point of Derived2::f() definition, there is no variable, function or type name f known by the compiler. The set of known entities that f could refer to is empty at this point. This isn't a problem because the compiler knows it will lookup f later as a function name, or a template function name.

OTOH, the compiler doesn't know what to do with d; it isn't a (called) function name. There is no way to do late binding on non-(called) functions names.

Now, all of this may seem like elementary knowledge of compile-time template polymorphism. The real question seems to be: why isn't d bound to Base<T>::d at template definition time?

The real issue is that there is no Base<T>::d at template definition time, because there is no complete type Base<T> at that time: Base<T> is declared, but not defined! You may ask: what about this:

template <typename T>
class Base {
public:
    int d;
};

it looks like the definition of a complete type!

Actually, until instantiation, it looks more like:

template <typename T>
class Base;

to the compiler. A name cannot be looked-up in a class template! But only in a template specialisation (instantiation). The template is a factory to make template specialisation, a template isn't a set of template specialisation. The compiler can lookup d in Base<T> for any particular type T, but it cannot lookup d in the class template Base. Until a type T is determined, Base<T>::d remains the abstract Base<T>::d; only when type T is known, Base<T>::d start to refer to a variable of type int.

The consequence of this is that the class template Derived2 has a complete base class Base0 but an incomplete (forward declared) base class Base. Only for a known type T, the "template class" (specialisations of a class template) Derived2<T> has a complete base classes, just like any normal class.

You now see that:

template <typename T>
class Derived : public Base<T> 

is actually a base class specification template (a factory to make base class specifications) that follows different rules from a base class specification inside a template.

Remark: The reader may have noticed that I have made-up a few phrases at the end of the explanation.

This is very different: here d is a qualified name in Derived<T>, and Derived<T> is dependent since T is a template parameter. A qualified name can be late-bound even if it isn't a (called) function name.

Yet another solution is:

template <typename T>
class Derived : public Base<T> {
    void f () {
        Derived::d = 0; // qualified name
    }
};

This is equivalent.

If you think that inside the definition of Derived<T>, the treatment of Derived<T> as a known complete class sometimes and as an unknown class some other times in inconsistent, well, you are right.

Paleoclimatology answered 26/10, 2011 at 20:30 Comment(1)
Moderator Note Comments under this answer have been purged because they degenerated into pure noise. Please use Stack Overflow Chat for extended discussions and remember to remain civil.Dogmatic
W
1

I would guess it pertains to an overloaded use of the Cleanup() routine. The type being passed is explicitly controlled by the template type T, which in turn can control which overloaded version of Cleanup() is invoked.

Willin answered 26/10, 2011 at 20:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.