Suppose a class
contains a type defined by a nested using
whose destructor needs to be explicitly invoked. Is it necessary to use using
to create a local type that doesn't include the namespace separator (::
)?
In this contrived example, I want to call A::WeakPtr
's destructor, like:
wp->~A::WeakPtr();
instead of like:
using AWeakPtr = A::WeakPtr;
wp->~AWeakPtr()
Is this doable? Here's a complete example.
#include <cstdlib>
#include <iostream>
#include <memory>
struct A : std::enable_shared_from_this<A> {
using SharedPtr = std::shared_ptr<A>;
using WeakPtr = std::weak_ptr<A>;
A() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
~A() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
};
int
main() {
{
std::unique_ptr<A::WeakPtr, void(*)(void*)>
uwp(static_cast<A::WeakPtr*>(std::malloc(sizeof(A::WeakPtr))), std::free);
A::WeakPtr* wp = uwp.get();
{
auto sp = std::make_shared<A>();
new(wp) A::WeakPtr(sp);
if (wp->lock())
std::cout << "Locked\n";
else
std::cout << "Unlocked\n";
}
if (wp->lock())
std::cerr << "EUNPOSSIBLE\n";
else
std::cout << "Unable to obtain lock\n";
// Need the following 'using' statement because the following is invalid syntax:
// wp->~A::WeakPtr();
using AWeakPtr = A::WeakPtr;
wp->~AWeakPtr();
// Is there a way to call A::WeakPtr without the using statement?
}
std::cout << "memory held by uwp has been free(3)'ed\n";
}
It seems like there should be a way to defeat the ::
namespace separator with a typename
scattered in there somewhere, but it doesn't look like it's possible. Obviously it's not the end of the world if it's not possible, but my curio is getting the better of me.
UPDATE
As suggested by @DanielFrey and @DyP's fantastic answer, the correct syntax is indeed
wp->A::WeakPtr::~WeakPtr();
but this doesn't work and is a bug (#12350) in clang++ (as of 2013-09-28).
The destructor call using a qualified-id here must consist of:
postfix-expression ->
nested-name-specifier ~
class-name ()
The postfix-expression here is wp
, and the part after ->
forms a single qualified-id (w/o the parens).
Grammatically, the following is also possible:
postfix-expression ->
nested-name-specifier ~
decltype-specifier ()
However, this second form is forbidden explicitly in [expr.prim.general]/9:
The form
~ decltype-specifier
also denotes the destructor, but it shall not be used as the unqualified-id in a qualified-id.
The class-name in the first form can also be a typedef-name [class.name]/5:
A typedef-name (7.1.3) that names a class type, or a cv-qualified version thereof, is also a class-name.
For the lookup of this class-name after the ~
, there's a special name lookup rule in [basic.lookup.qual]/5:
Similarly, in a qualified-id of the form:
nested-name-specifieropt class-name:: ~
class-name
the second class-name is looked up in the same scope as the first.
This means that the second WeakPtr
in A::WeakPtr :: ~WeakPtr
should be found. It is a typedef-name naming a class, therefore a class-name, and it's looked up in the scope of A
. gcc follows this rule, clang++3.4 does not.
Therefore, wp->A::WeakPtr :: ~WeakPtr();
as suggested by Daniel Frey (and my first, deleted comment/guess) should work.
Alternative approaches:
Using a helper function:
template<class T>
void destroy(T& t)
{ t.~T(); }
Using a decltype-specifier w/o a qualified-id. This one is tricky, as the type of decltype(*wp)
is A::WeakPtr&
, as *wp
yields an lvalue. However, we can convert the the expression to a prvalue to get rid of the reference:
wp->~decltype((A::WeakPtr)*wp)();
// alternatively, w/o explicitly mentioning the type:
wp->~decltype((std::remove_pointer<decltype(wp)>::type)*wp)();
// simpler, using a helper function again:
template<class T> T helper(T const&);
wp->~decltype(helper(*wp))();
Production:
Begin with the function call [expr.post]/1:
postfix-expression
(
expression-listopt)
Where the postfix-expression here is produced via:
postfix-expression
-> template
opt id-expression
This postfix-expression here maps to wp
(in wp->~something()
).
The id-expression contains the destructor "name" [expr.prim.general]:
id-expression:
unqualified-id
qualified-id
We do need a qualified-id here, so [expr.prim.general]/8:
qualified-id:
nested-name-specifiertemplate
opt unqualified-id
::
identifier
::
operator-function-id
::
literal-operator-id
::
template-id
Only the first one is of interest, so we look at unqualified-id s:
unqualified-id:
identifier
operator-function-id
conversion-function-id
literal-operator-id
~
class-name
~
decltype-specifier
template-id
Where the two with a ~
can be used to call a destructor.