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