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
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 似乎改变了这一点。我不确定这是否会作为一个缺陷在旧版本中被追溯修复,因为似乎没有编译器真正遵循这个迂腐的要求。
最新版本的 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
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 似乎改变了这一点。我不确定这是否会作为一个缺陷在旧版本中被追溯修复,因为似乎没有编译器真正遵循这个迂腐的要求。