类模板的名称是否在限定的外部析构函数定义的范围内?

Hum*_*ler 5 c++ templates clang clang++

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

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

template <int A>
foo::Bar<A>::~Bar(){}
Run Code Online (Sandbox Code Playgroud)

生成的警告(和错误-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
Run Code Online (Sandbox Code Playgroud)

Live Example

clang是我见过的第一个发出此诊断的编译器,据我所知,上面写的内容是完全有效的 C++。似乎抑制这种情况的两种不同方法是在同一名称空间内定义 显式限定析构函数名称——例如:

<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
Run Code Online (Sandbox Code Playgroud)

Clang 在这里的诊断是否正确?我的理解是类型的名称绝对在正确的名称范围内foo::Bar<A>::~- 并且~Bar<A>()应该没有必要限定。

Hum*_*ler 5

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

\n

根据 Richard Smith 在LLVM Bug 46979中的说法:

\n
\n

根据书面标准规则,诊断正确;严格来说,C++ 规则要求此析构函数写为

\n
template<typename T>\nA::B<T>::~B<T>()\n
Run Code Online (Sandbox Code Playgroud)\n

根据 C++ [basic.lookup.qual]p6:

\n
\n

在以下形式的限定 ID 中:

\n

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

\n

第二个类型名称在与第一个类型名称相同的范围内查找。

\n
\n

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

\n

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

\n
\n

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

\n

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

\n
\n

如果限定名 Q 跟在 ~ 后面:

\n

(4.1)如果 Q 是成员限定名称,则它会经历非限定查找和限定查找。

\n

(4.2)否则,其嵌套名称说明符 N 应指定一个类型。\n如果 N 有另一个嵌套名称说明符 S,则查找 Q,就好像其查找上下文是由 S 指定的一样。

\n

(4.3)否则,如果 N 的终端名称是成员限定名称 M,则查找 Q,就好像 ~Q 出现在 M 的位置一样(如上所述)。

\n

(4.4)否则,Q 将进行不合格查找。

\n

(4.5) Q 的每次查找仅考虑类型(如果 Q 后面没有跟 <)和专门化为类型的模板。\n如果没有找到任何内容或不明确,则将其丢弃。

\n

(4.6)是或包含 Q 的类型名称应在至少执行一次(成功)查找所建立的解释下引用其(原始)查找上下文(忽略 cv 限定)。

\n

[示例4:

\n
struct C {\n  typedef int I;\n};\ntypedef int I1, I2;\nextern int* p;\nextern int* q;\nvoid f() {\n  p->C::I::~I();        // I is looked up in the scope of C\n  q->I1::~I2();         // I2 is found by unqualified lookup\n}\nstruct A {\n  ~A();\n};\ntypedef A AB;\nint main() {\n  AB* p;\n  p->AB::~AB();         // explicitly calls the destructor for A\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x94结束示例]

\n
\n

(完整的引文已发布在此处,因为这是草稿,措辞将来可能会发生变化)

\n

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

\n

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

\n