Деструктор псевдонима typedef

#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();
}

Выходной сигнал вышеуказанной программы:

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

Я предполагаю, что первые две строки принадлежат вызовам деструктора пользователя, а третья - из-за вызова деструктора при выходе из области main функции.

По моему мнению, typedef является псевдонимом для типа. В этом случае AB является псевдонимом для A

Почему это относится и к имени деструктора? Ссылка на спецификацию языка очень ценится.

Изменение: он был скомпилирован с использованием Apple LLVM версии 9.1.0 (clang-902.0.39.1) на macOS High Sierra версии 10.13.3.

Ответ 1

Почему это относится и к имени деструктора?

Потому что стандарт говорит:

[Class.dtor]

В явном вызове деструктора деструктор задается символом ~, за которым следует имя типа или спецификатор decltype, который обозначает тип класса деструкторов....

Атрибут typedef - это имя типа, которое обозначает тот же класс, что и имя типа самого класса.

У правила даже есть поясняющий пример:

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 destructor
  B_ptr->~B();                  // calls D destructor
  B_ptr->~B_alias();            // calls D destructor
  B_ptr->B_alias::~B();         // calls B destructor
  B_ptr->B_alias::~B_alias();   // calls B destructor
}

Дальнейшая спецификация поиска имени, а также пример, который относится к вопросу:

[Basic.lookup.qual]

Если псевдо-деструктор-имя ([expr.pseudo]) содержит спецификатор вложенных имен, имена типов рассматриваются как типы в области, назначенной вложенным именем-спецификатором. Аналогично, в квалифицированном id формы:

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

второе имя класса просматривается в той же области, что и первая. [ Пример:

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
}

- конец примера]

Ответ 2

Потому что, когда вы пишете ~AB() вы не называете или не вызываете деструктор. Вы пишете ~ с последующим именем класса, и вызов деструктора автоматически предоставлен в результате указанной семантики написания этих маркеров рядом друг с другом.

Обычно это академический, но здесь вы видите, почему это может иметь значение.

Точно так же, написав AB() вы не "вызываете конструктор", хотя это выглядит как вызов функции, и многие новички языка интерпретируют этот код таким образом. (Это может привести к забавному и игровому процессу при попытке вызвать конструктор шаблона без вывода аргумента: не имея возможности назвать конструктор, нет способа предоставить эти аргументы!)

Фактически, ни конструктор, ни деструктор технически даже не имеют имени!

Эти нюансы делают C++ забавой, не так ли?