How to explicitly call a namespace-qualified destr

2019-02-11 12:46发布

问题:

I am surprised that the following simple code won't compile (with gcc, version 4.8.1)

#include <string>
void test()
{
  std::string* p = new std::string("Destruct me");
  p->std::~string();
}

It says: error: scope ‘std’ before ‘~’ is not a class-name. Yet reading the standard, I would say the syntax says it should be "postfix-expresssion -> pseudo-constructor-name", where pseudo-constructor-name can be of the form "nested-name-specifier ~ type-name", and nested-name-specifier can be "identifier::".

Leaving out "std::" leads to the complaint that a class name was expected before the left paren, and putting it after the tilde to the complaint that a class name was expected before "::". After some trying I found that it will compile when written p->std::string::~string(); (but not when one writes p->std::string::~std::string(); instead). But qualifying the destructor with its own type name is not a neutral operation; I gather from the example in 12.4:13 of the standard (but curiously not from the normative text) that this forces the destructor of the exact static (base) class to be called, rather than as a virtual function that of (the most derived type of) the actual object pointed to. Here it makes no difference, but in similar cases it would; why would the syntax force exclusively using the static type?

However, with clang instead of gcc even the mentioned variant gives a syntax error. The error messages of clang are more amusing though, if you're in the mood for for this kind of humour when reading error messages: for p->std::string::~string(); it gives "expected the class name after '~' to name a destructor" (and so it does; one wonders which kind of class names would not name a destructor if prefixed by a tilde), and for my initial trial p->std::~string() it retorts "qualified member access refers to a member in namespace 'std'" (again one wonders what is wrong with that; indeed the destructor to be called lives in the namespace 'std'). I've tried all 8 reasonable combinations (std:: and/or string:: before the tilde, and/or std:: after it) and none of them compile with clang.

I can make it compile, even with clang, using using std::string;. But what I find curious is that I can find no indication in the standard that such a declaration was intended to be necessary in such cases.In fact I can find nothing that addresses the issue of calling the destructor of a namespace-qualified class at all. Am I missing something obvious?

As a final note, I'd like to add that it strikes me as odd that one have to use a namespace qualification at all when calling a destructor. Since this is member access from a well specified object (here *p) shouldn't argument-dependent lookup make explicitly qualifying the namespace unnecessary?

回答1:

In the standard, at:

§3.4.5/3

If the unqualified-id is ~type-name, the type-name is looked up in the context of the entire postfix-expression.

therefore it would seem that ~string should be looked up in the context of the std:: namespace.

In fact, considering that a corresponding home-made version works as follows on both GCC and Clang:

namespace STD {
class STRING {};
}

int main() {
    STD::STRING* a = new STD::STRING();
    a->~STRING();
}

Live demo with clang++ Live demo with g++

I'll go ahead and say this is most likely a bug.


Apparently, given that std::string is really std::basic_string<char> if you call:

a->~basic_string();

Live demo with clang++ Live demo with g++

then everything compiles fine.

I still remain of the idea that this a bug, considering that the following example (taken from the standard), shows that typedefs should also work:

struct B {
    virtual ~B() { }
};

struct D : B {
    ~D() { } 
};

D D_object;

typedef B B_alias;

B* B_ptr = &D_object;

void f() {
D_object.B::~B();
    B_ptr->~B();
    B_ptr->~B_alias();
    B_ptr->B_alias::~B();
    B_ptr->B_alias::~B_alias();
}

This notion, together with §3.4.5/3 should guarantee that:

p->~string();

should work.



回答2:

2019 update: Starting from C++17, you can use std::destroy_at as follows:

std::destroy_at(p);

It's much simpler and follows the principle of not using "primitive constructs" (such as new / delete expressions) in modern C++.