On cppreference.com – “Qualified name lookup”, I found this strange code example:
struct C { typedef int I; };
typedef int I1, I2;
extern int *p, *q;
// Slight modification to prevent linker error
//struct A { ~A(); };
struct A { ~A() {}; };
typedef A AB;
int main()
{
p->C::I::~I(); // The name I after ~ is looked up in the same scope as I before ::
// (that is, within the scope of C, so it finds C::I)
q->I1::~I2(); // The name I2 is looked up in the same scope as I1
// (that is, from the current scope, so it finds ::I2)
AB x;
x.AB::~AB(); // The name AB after ~ is looked up in the same scope as AB before ::
// (that is, from the current scope, so it finds ::AB)
}
To my surprise, this compiles without any errors. But what is even going in the lines
p->C::I::~I();
q->I1::~I2();
? Not only does this look like it’s accessing members of int
variables and somehow referring to an int
destructor, but p
and q
are also extern
variables without any definition.
How come that this syntax is allowed, and what does it actually do?
and somehow referring to an int destructor,
These are pseudo-destructor calls. The direct notation not using the qualified lookup of type aliases would be
p->~int();
q->~int();
The notation x.~T()
or p->~T()
is always a pseudo-destructor call if T
is a scalar type, rather than a class type. And in that case x
must have type T
and p
type int*
.
A pseudo-destructor call ends the lifetime of the object x
/*p
, just like a normal destructor call, but doesn't do anything else. In particular it doesn't call any destructor (which scalar types do not have). It exists only so that one can write generic code that works for both class and non-class types without having to special case destructor calls.
Not only does this look like it’s accessing members of int variables
This is explained in the comments and the linked cppreference article itself. The notation p->
followed by a qualified name does not imply that a member of the type of *p
is looked up. Instead lookup may also consider the current scope, given the rules provided in the article. However, if *p
is not a class type, then a pseudo-destructor call is the only result of such a lookup that wouldn't cause the expression to end up ill-formed.
but p and q are also extern variables without any definition.
The pseudo-destructor calls odr-use p
and q
, so a definition for them must exist in the program. However, it may be in a different translation unit. And even if it doesn't exist, such an odr violation makes the program IFNDR (ill-formed, no diagnostic required), so the compiler doesn't have to complain about it. The lines don't actually cause any machine code to be emitted after all, so there is no reason to bother the linker to look for a definition of p
and q
.