The STL code I am reading may be old...but the question is more related to C++ template grammar.
The question surrounds this stl template function:
template<class T> std::destroy(T *p) {
p->~T();
}
I can't seem to find a specialization of the std::destroy(T *) function. So it seems to me that the template function will instantiate same for "int" types, and invoke "int"'s destructor. To make my point, I created this sample code that emulate the std::destroy. I call it my_destroy ih the example.
#include <iostream>
#include <stdio.h>
using namespace std;
template <class T>
void my_destroy(T * pointer) {
pointer->~T();
}
int main()
{
int *a;
//a->~int(); // !!! This won't compile.
my_destroy<int>(a); // !!! This compiles and runs.
}
}
To my surprise, this line doesn't compile:
a->~int();
But this line compiles:
my_destroy<int>(a);
My confusion is, I thought that my_destroy<int>(a)
will be instantiated as the equivalent of a->~int();
To a question in larger context, when a STL container of <int>
erases an element, how does the std::destroy()
work?
Note that, while a->~int();
doesn't compile, this does:
typedef int INT;
int* a;
a->~INT();
From the standard:
5.2.4p1 The use of a pseudo-destructor-name
after a dot . or arrow -> operator represents the destructor for the non-class type denoted by type-name
or decltype-specifier
. The result shall only be used as the operand for the function call operator (), and the result of such a call has type void. The only effect is the evaluation of the postfix-expression before the dot or arrow.
From 5.2p1:
pseudo-destructor-name:
nested-name-specifier_opt type-name :: ~ type-name
nested-name-specifier template simple-template-id :: ~ type-name
nested-name-specifier_opt~ type-name
~ decltype-specifier
And finally, 7.1.6.2p1:
type-name:
class-name
enum-name
typedef-name
simple-template-id
So, curiously, int
is not syntactically a type-name
(it's a simple-type-specifier
) and so you can't call ~int()
, but INT
is and so you can.
For class types (non-scalar types), the expression
pointer->~T();
is essentially a function call (a postfix-expression). To identify the function to be called, the part pointer->~T
is must be analysed. The ~T
is an id-expression IFF T
is a class-name, identifying the destructor function.
Of course, int
is not a class name. But if T
is a type-name naming a scalar type, the same expression is parsed differently. The whole part pointer->~T
is identified as a special postfix-expression called a pseudo-destructor name. With the following ()
the expression is considered a call of a pseudo-destructor (the rules in [expr.pseudo] forbid to do anything else with a pseudo-destructor name but to call it).
int
itself is not a type-name [dcl.type.simple], but a simple-type-specifier:
type-name:
- class-name
- enum-name
- typedef-name
- simple-template-id
That's why you can use a typedef
'd int
or T
as in your example (*), but not int
directly. The reasoning has been well explained by sehe.
The rules for pseudo-destructor calls are specified in [expr.pseudo]: "The only effect is the evaluation of the postfix-expression before the dot or arrow."
(*) from [temp.param]/3: "A type-parameter whose identifier does not follow an ellipsis defines its identifier to be a typedef-name [...]"
The language allows these things to enable generic programming. However, your 'straight' invocation is not in generic code, hence it fails.
Another such case is
template <typename T> void foo()
{
T instance = T(); // default constructor
}
foo<int>(); // will work
So, yes:
template <typename T> void foo()
{
T* instance = new T(); // default constructor
delete instance;
}
will also work for builtin primitive types, because T
is a typename in the scope ot the template.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With