class 模板的名称是否在合格的外联析构函数定义的范围内?

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

最新版本的 clang(自 clang-11 起)在使用 -pedantic 编译以下代码段时发出警告:

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

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

生成的警告(以及 -Werror 的错误)为:

<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 是我见过的第一个发出此诊断的编译器,据我所知,上面写的是完全有效的 C++。似乎抑制这种情况的两种不同方式是 define within the same namespace or explicitly qualify the destructor name——例如:

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

这里 Clang 的诊断是否正确? 据我了解,该类型的名称绝对会在正确的名称范围内,如 foo::Bar<A>::~ - 并且~Bar<A>() 应该是不必要的。

从发布问题后我了解到的情况来看,严格来说,这个警告是正确的——尽管它很可能是标准措辞中的一个缺陷。

根据 Richard Smith 在 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.

这意味着第二个 B 在 class A 中查找,它只找到 class 模板 B 而不是注入的-class-名称。

这不是一个特别有用的规则,并且可能不是预期的规则,这就是为什么默认情况下禁用此诊断(以及一堆类似的诊断,用于看似合理但形式上不正确的析构函数名称)但包含在-迂腐。

进一步研究,我可以在 C++20 标准中找到提到的 [basic.lookup.qual]/6 段落,但 C++23 的草案似乎已经改变了这一点——这很可能指向这一点是一个缺陷。

在 C++23 的 [basic.lookup.qual] 草案中,整个部分已经过大修,目前,在撰写本文时,被 [basic.lookup.qual]/4 取代,其中指出:

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]

(由于这是草稿,因此已在此处发布了完整的引文,未来可能会更改措辞)

这似乎通过确保按预期执行查找来明确更正此问题。

基本上,由于措辞上的缺陷,诊断在旧版本的 C++ 下是正确的——但 C++23 似乎改变了这一点。我不确定这是否会作为一个缺陷在旧版本中被追溯修复,因为似乎没有编译器真正遵循这个迂腐的要求。