Is a class template's name in scope for a qualified out-of-line destructor's definition?
Asked Answered
E

1

12

Recent versions of clang (since clang-11) issue a warning when compiled with -pedantic for the following segment of code:

namespace foo {
    template <int A>
    struct Bar {
        ~Bar();
    };
} // namespace foo 

template <int A>
foo::Bar<A>::~Bar(){}

With the generated warning (and error with -Werror) being:

<source>:10:12: error: ISO C++ requires the name after '::~' to be found in the same scope as the name before '::~' [-Werror,-Wdtor-name]
foo::Bar<A>::~Bar(){}
~~~~~~~~~~~^~
           ::Bar

Live Example

clang is the first compiler I've seen to issue this diagnostic, and as far as I'm aware, what was written above is completely valid C++. Two different ways that seem to suppress this is to either define within the same namespace or explicitly qualify the destructor name -- such as:

...
template <int A>
foo::Bar<A>::~Bar<A>(){}

Is Clang's diagnostic here correct? It's my understanding that the type's name would absolutely be in the correct name-scope as foo::Bar<A>::~ -- and that the qualification of ~Bar<A>() should be unnecessary.

Electioneer answered 12/8, 2021 at 4:37 Comment(0)
E
4

From what I have learned since posting the question, this warning is, strictly-speaking, correct -- though it most likely a defect in the wording of the standard.

According to Richard Smith in LLVM Bug 46979:

The diagnostic is correct, per the standard rules as written; strictly-speaking, the C++ rules require this destructor to be written as

template<typename T>
A::B<T>::~B<T>()

Per C++ [basic.lookup.qual]p6:

In a qualified-id of the form:

nested-name-specifier[opt] type-name :: ~ type-name

the second type-name is looked up in the same scope as the first.

This means that the second B is looked up in class A, which finds only the class template B and not the injected-class-name.

This is not an especially useful rule, and likely isn't the intended rule, which is why this diagnostic (along with a bunch of similar diagnostics for plausible but formally incorrect destructor names) is disabled by default but included in -pedantic.

Looking further into this, I can find the mentioned passage for [basic.lookup.qual]/6 in the C++20 standard, but it appears drafts for C++23 have changed this -- which points towards this most likely being a defect.

In drafts for [basic.lookup.qual] for C++23, this whole section has been overhauled and is currently, at the time of writing, replaced with [basic.lookup.qual]/4 which states:

If a qualified name Q follows a ~:

(4.1) If Q is a member-qualified name, it undergoes unqualified lookup as well as qualified lookup.

(4.2) Otherwise, its nested-name-specifier N shall nominate a type. If N has another nested-name-specifier S, Q is looked up as if its lookup context were that nominated by S.

(4.3) Otherwise, if the terminal name of N is a member-qualified name M, Q is looked up as if ~Q appeared in place of M (as above).

(4.4) Otherwise, Q undergoes unqualified lookup.

(4.5) Each lookup for Q considers only types (if Q is not followed by a <) and templates whose specializations are types. If it finds nothing or is ambiguous, it is discarded.

(4.6) The type-name that is or contains Q shall refer to its (original) lookup context (ignoring cv-qualification) under the interpretation established by at least one (successful) lookup performed.

[Example 4:

struct C {
  typedef int I;
};
typedef int I1, I2;
extern int* p;
extern int* q;
void f() {
  p->C::I::~I();        // I is looked up in the scope of C
  q->I1::~I2();         // I2 is found by unqualified lookup
}
struct A {
  ~A();
};
typedef A AB;
int main() {
  AB* p;
  p->AB::~AB();         // explicitly calls the destructor for A
}

— end example]

(The full quote has been posted here since this is from a draft, and the wording may be subject to change in the future)

This appears to explicitly correct this issue by ensuring that lookup is performed as one would expect.

So basically the diagnostic is correct under older versions of C++ due to a defect in the wording -- but C++23 appears to change that. I am not sure whether this will be retroactively fixed in older versions as a defect given that it appears no compiler actually follows this pedantic requirement.

Electioneer answered 12/8, 2021 at 21:49 Comment(1)
C++23 appears to change that Doesn't «If N has another nested-name-specifier S, Q is looked up as if its lookup context were that nominated by S» mean the same thing as «the second type-name is looked up in the same scope as the first»?Randy

© 2022 - 2024 — McMap. All rights reserved.