Reputation: 363817
I want to explicitly destroy a vector
in a templated context. The following works for me (GNU C++ 4.3, 4.4 and Clang++ 1.1):
template <typename T>
void destroy_vector_owner(VectorOwner<T> *obj)
{
obj->v.~vector();
// further cleanup by Python API functions omitted
}
while it fails on Mac OS X v10.5's g++
(i686-apple-darwin10-gcc-4.2.1
) with
expected class-name before ‘(’ token
If I change it to
obj->v.~vector<T>();
the code fails to compile with G++, but Clang can still handle it. Which is the correct idiom? Are any of these compilers known to be broken in this regard?
Update: the definition of VectorOwner
is
template <typename T>
struct VectorOwner {
PyObject_HEAD
std::vector<T> v;
};
This is a Python object that has to keep an std::vector
alive. I admit that the construct is slightly dangerous, but I need the compact storage, amortized O(1) push_back
and the ability to steal another vector's contents with the swap
member.
Upvotes: 11
Views: 2864
Reputation: 39109
My first answer was wrong actually, litb pointed me into the right direction. The right answer is that both syntaxes are correct:
The syntax for an explicit destructor call is described in 12.4 Destructors
:
12 In an explicit destructor call, the destructor name appears
as a ˜ followed by a type-name that names the destructor’s
class type. The invocation of a destructor is subject to the
usual rules for member functions (9.3) [...]
type-name
can be found in 7.1.5.2 Simple type specifiers
:
type-name:
class-name
enum-name
typedef-name
class-name
is described in 9. Classes
:
class-name:
identifier
template-id
So a destructor call is, simplified, one of the following
foo.~typedef-name ()
foo.~identifier ()
foo.~template-id ()
We neither have a typedef-name here, nor a simple identifier, so only foo.~template-id()
is left
for us.
We also find in 14. Templates
3 After name lookup (3.4) finds that a name is a template-name,
if this name is followed by a <, the < is always taken as the
beginning of a template-argument-list and never as a name
followed by the less-than operator.
So the compiler must assume in your example that the <
is the beginning
of a template-argument-list.
Also, if your destructor would be a template (...), then
4 When the name of a member template specialization appears
after . or -> in a postfix-expression, or after nested-name-specifier
in a qualified-id, and the postfix-expression or qualified-id explicitly
depends on a template-parameter (14.6.2), the member template name must
be prefixed by the keyword template. Otherwise the name is assumed to
name a non-template.
So because you did not prefix your destructor call f.~foo<int>
with template, i.e.
like f.template ~foo<int>
, the compiler must assume that your destructor
is NOT a template.
Further,
6 A template-id that names a class template specialization
is a class-name (clause 9).
So ~foo<int>
names your template specialization foo<int>
and therefore is a class-name
,
a class-name
is by the grammar rules a type-name
, and a ~
followed by a typename
is
a destructor call. Therefore
foo<int> f;
f.~foo<int>(); // valid
But also
f.~foo(); // valid
Because 3.4.5 Class member access
:
3 If the unqualified-id is ˜type-name, and the type of the object expression
is of a class type C (or of pointer to a class type C), the type-name is
looked up in the context of the entire postfix-expression and in the scope of
class C. [...]
thus in f.~foo();
, foo
is looked up within f.
, and within the scope of foo<int>
, it is valid
to refer to it just with with foo
.
And finally, 14.3 contains the one-and-for-all-permission:
5 An explicit destructor call (12.4) for an object that
has a type that is a class template specialization may
explicitly specify the template-arguments. [Example:
template<class T> struct A {
˜A();
};
void f(A<int>* p, A<int>* q) {
p->A<int>::˜A(); // OK: destructor call
q->A<int>::˜A<int>(); // OK: destructor call
}
—end example]
Upvotes: 6
Reputation: 35469
From n3290, 3.4.5 Class member access [basic.lookup.classref]
3 If the unqualified-id is ~type-name, the type-name is looked up in the context of the entire postfix-expression. If the type T of the object expression is of a class type C, the type-name is also looked up in the scope of class C. At least one of the lookups shall find a name that refers to (possibly cv-qualified) T. [...]
Following that is an example (as a non-normative note) which contains the following snippet of code:
a->~A(); // OK: lookup in *a finds the injected-class-name
In particular, for template<typename T, typename Allocator> class vector;
, vector
is the injected-class-name. For that reason, I believe
obj->v.~vector();
is correct.
(I don't have anything to say about ~vector<T>
at the moment.)
Upvotes: 3