Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to explicitly call a namespace-qualified destructor?

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?

like image 245
Marc van Leeuwen Avatar asked Jul 06 '14 08:07

Marc van Leeuwen


2 Answers

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.

like image 179
Shoe Avatar answered Sep 18 '22 05:09

Shoe


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

like image 24
Daniel Langr Avatar answered Sep 20 '22 05:09

Daniel Langr