Destructor of typedef alias

#include <iostream>

struct A { ~A(); };
A::~A() {
std::cout << "Destructor was called!" << std::endl;
}

typedef A AB;
int main() {
AB x;
x.AB::~AB(); // Why does this work?
x.AB::~A();
}


The output of the above program is:

Destructor was called!
Destructor was called!
Destructor was called!


I assume the first two lines belonging to user destructor calls, while the third line is due to the destructor being called when exiting the scope of main function.

From my understanding a typedef is an alias for a type. In this case AB is an alias for A.

Why does this apply for the name of the destructor too? A reference to the language specification is very much appreciated.

Edit: This was compiled using Apple LLVM version 9.1.0 (clang-902.0.39.1) on macOS High Sierra Version 10.13.3.


Why does this apply for the name of the destructor too?


Because standard says:


[class.dtor]

In an explicit destructor call, the destructor is specified by a ~ followed by a type-name or decltype-specifier
that denotes the destructor’s class type. ...


A typedef alias is a type-name which denotes the same class as the type-name of the class itself.

The rule even has a clarifying example:


struct B {
virtual ~B() { }
};
struct D : B {
~D() { }
};

D D_object;
typedef B B_alias;
B* B_ptr = &D_object;

void f() {
D_object.B::~B(); // calls B's destructor
B_ptr->~B(); // calls D's destructor
B_ptr->~B_alias(); // calls D's destructor
B_ptr->B_alias::~B(); // calls B's destructor
B_ptr->B_alias::~B_alias(); // calls B's destructor
}





Further specification about the name lookup, also with an example that applies to the question:


[basic.lookup.qual]

If a pseudo-destructor-name ([expr.pseudo]) contains a
nested-name-specifier, the type-names are looked up as types in the
scope designated by the nested-name-specifier. Similarly, in a
qualified-id of the form:

nested-name-specifieropt class-name :: ~ class-name

the second class-name is looked up in the same scope as the first.
[ Example:

struct C {
typedef int I;
};
typedef int I1, I2;
extern int* p;
extern int* q;
p->C::I::~I(); // I is looked up in the scope of C
q->I1::~I2(); // I2 is looked up in the scope of the postfix-expression

struct A {
~A();
};
typedef A AB;
int main() {
AB* p;
p->AB::~AB(); // explicitly calls the destructor for A
}


— end example  ]


Because when you write ~AB() you are not naming or calling the destructor. You are writing ~ followed by the a name of the class, and the destructor call is automatically provisioned as a result of the specified semantics of writing those tokens next to each other.

Usually this is academic but here you see why it can matter.

Similarly, by writing AB() you are not "calling a constructor", even though this looks like a function call and many language newcomers interpret the code this way. (This can lead to fun and games when attempting to call a template constructor without argument deduction: without being able to name the constructor, there's no way to provide those arguments!)

In fact, neither the constructor nor the destructor technically even have a name!

These nuances make C++ fun, right?

Comments

Popular posts from this blog

Meaning of `{}` for return expression

Get current scroll position of ScrollView in React Native

React Native - Image Cache