14

Consider this simple code.

struct Foo {
    template <typename T>
    struct Bar {
        ~Bar();
    };
};

with the out-of-line implementation

template <typename T>
Foo::Bar<T>::~Bar()
{
    // dtor implementation
}

Using clang (up to current trunk), with -std=c++20 and -Wdtor-name issues a warning: warning: ISO C++ requires the name after '::~' to be found in the same scope as the name before '::~' [-Wdtor-name], and requires the implementation be changed to this.

template <typename T>
Foo::Bar<T>::~Bar<T>()
{
    // dtor implementation
}

But, CWG 2237 makes this invalid (at least in a plain class template). Maybe it wasn't intended to extend to a nested class template, or maybe clang has a bug?

FWIW, gcc (current trunk) accepts the first one, and issues a compile warning for the second: warning: template-id not allowed for destructor in C++20 [-Wtemplate-id-cdtor]

Note that when using -Werror (which I do), these warnings are errors.

I assume this is a clang bug, but before I filed one, I wanted to make sure my understanding wasn't way off base.

5
  • I am not a language lawyer, but this seems like the tag would be well-applied here. Commented Sep 18 at 14:38
  • 4
    Clang appears to be suggesting template<typename T> Foo::Bar<T>::Bar::~Bar(), not ~Bar<T>(): godbolt.org/z/68nvhhzEe Commented Sep 18 at 15:19
  • @Artyer Hmmm. I didn't read the message that way, so my solution was to append the id. However, your interpretation seems to be correct. Your change satisfies clang and compiles with gcc as well, and seems to satisfy the standard as well. If you want to specify your comment as an answer, I'd accept it - looks like clang is just a bit gracious of the old format and I was misinterpreting their error message. Commented Sep 18 at 15:42
  • 3
    That change seems very weird to me, because it's adding a level of indirection that doesn't seem to exist in the class structure. Commented Sep 18 at 15:49
  • 2
    If its any help (or to increase the weirdness quotient), the name is injected into its own scope. So you could do... Foo::Bar<T>::Bar::Bar::Bar::Bar::~Bar(). Which is kind of like having a file path /Users/jodyh/proj/././././Foo.cpp. Commented Sep 18 at 16:12

2 Answers 2

11

This is the warning for CWG244. It is saying that in "nested-name-specifier class-name :: ~ class-name", the second class-name is looked up in the same scope as the first nested-name-specifier, and not in the scope of the first class-name. Clang is claiming to use an extension to search inside the first class-name to find the destructor (because searching inside Foo:: will not be able to find a ~Bar).

The warning is suggesting you write it like:

template <typename T>
Foo::Bar<T>::Bar::~Bar()
{
    // dtor implementation
}

Where now the first nested-name-specifier is Foo::Bar<T>::, which you will be able to find ~Bar in.


I'm not sure this warning is actually correct. The part about destructor lookup (now at [basic.lookup.qual.general]p4 was changed heavily by P1787R6, which the discussion around CWG244 and related issues seems to say solves this

Sign up to request clarification or add additional context in comments.

Comments

6

@Artyer's analysis is correct, but note that CWG244 concerns name lookup in a destructor call. Clang is wrong in applying the rule to a destructor declaration.

[dcl.meaning.general]/3 specifies that the name after ::~ in a declaration is not looked up:

  • If the declarator declares an explicit instantiation or a partial or explicit specialization, the declarator does not bind a name. If it declares a class member, the terminal name of the declarator-id is not looked up; [...]
  • Otherwise, the terminal name of the declarator-id is not looked up.

[class.dtor]/1 specifies that, in a destructor declaration, the name after ~ must be the injected-class-name of the enclosing class:

  • in a member-declaration that belongs to the member-specification of a class or class template but is not a friend declaration ([class.friend]), the id-expression is ~class-name and the class-name is the injected-class-name ([class.pre]) of the immediately-enclosing entity or
  • otherwise, the id-expression is nested-name-specifier ~class-name and the class-name is the injected-class-name of the class nominated by the nested-name-specifier.

For Foo::Bar<T>, the injected-class-name is Bar. So Foo::Bar<T>::~Bar is the correct form, and Clang is incorrect in rejecting it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.