Search code examples
c++g++standardsclang++

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?


Solution

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