Explicit destructor in templated context

2019-02-16 17:24发布

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.

3条回答
男人必须洒脱
2楼-- · 2019-02-16 17:45

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.)

查看更多
唯我独甜
3楼-- · 2019-02-16 17:50

You can try following syntax it works in gcc as well:

obj->v.template ~vector<T>();

Demo.

查看更多
三岁会撩人
4楼-- · 2019-02-16 18:07

My first answer was wrong actually, litb pointed my into the right direction. The right answer is that both syntaxes are correct:


Destructor call syntax.

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.


Compiler's assumption on destructor call with template-arguments.

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.

Backtrack.

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

Destructor call without template-arguments.

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.


The standard is actually explicit on this topic, d'oh.

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]
查看更多
登录 后发表回答