Clang 是不是正确拒绝仅通过特化定义类模板的嵌套类的代码?

Posted

技术标签:

【中文标题】Clang 是不是正确拒绝仅通过特化定义类模板的嵌套类的代码?【英文标题】:Is Clang correct to reject code in which the nested class of a class template is defined only via specializations?Clang 是否正确拒绝仅通过特化定义类模板的嵌套类的代码? 【发布时间】:2020-03-15 02:59:15 【问题描述】:

给定以下类模板:

template<typename T>
struct Outer

    struct Inner;

    auto f(Inner) -> void;
;

我们为Outer的每个特化分别定义Inner

template<>
struct Outer<int>::Inner ;

template<>
struct Outer<double>::Inner ;

然后为Outer的所有特化定义一次成员函数f

auto Outer<T>::f(Inner) -> void



但是 Clang (9.0.0) 抱怨:

error: variable has incomplete type 'Outer::Inner'

auto Outer<T>::f(Inner) -> void

                      ^

我们还可以通过为Outer 的所有其他特化提供Inner 的定义来规避编译器错误:

template<typename T>
struct Outer<T>::Inner ;

或为每个专业分别定义f

template<>
auto Outer<int>::f(Inner) -> void




template<>
auto Outer<double>::f(Inner) -> void



GCC 和 MSVC 都接受初始代码,这就引出了问题;这是一个 Clang 错误还是三个中唯一符合的实现?

Try on Compiler Explorer

【问题讨论】:

Inner 的特殊性无关紧要,删除它们不会改变编译结果。 @n.'pronouns'm。我不确定你是什么意思。 adding a definition of Inner for all other specializations 和 defining f separately for each specialization 都解决了编译错误。 我们再读一遍:删除它们并不会改变编译结果。不添加,不删除。 gccclang @n.'pronouns'm。我现在明白你的意思了,但这仍然是一个奇怪的评论。我的问题的重点是 Inner 被报告为不完整的类型,尽管提供了 Outer 的每个专业化的定义。如果您删除它的定义,Inner 显然会(正确地)是一个不完整的类型。 "如果您删除它的定义,显然 Inner 将(正确地)是一个不完整的类型。" 不,这根本不是 ckear。特化是一个完全独立的模板,它不会完全影响主模板。 【参考方案1】:

我认为 Clang 拒绝您的代码是错误的。我们必须问自己,您的函数声明和定义与

auto f(typename T::Inner) -> void;

// ...

template<typename T>
auto Outer<T>::f(typename T::Inner) -> void
 

在这个例子中,T::Inner 显然是一个依赖类型。所以 Clang 可能不会假设它在实例化之前是不完整的。在您的示例中也是如此吗?我会这么说。因为我们在标准中有这个:

[temp.dep.type]

5一个名字是一个当前实例化的成员如果它是

一个非限定名称,在查找时,它指的是当前实例化或非依赖类的至少一个成员 其基类。 [ 注意:这只能在查找名称时发生 在类模板定义所包含的范围内。 - 结尾 注意 ] ...

一个名字是一个当前实例化的依赖成员如果它是 当前实例化的成员,在查找时指代 当前实例化的类的至少一个成员。

9一个类型是依赖的,如果是的话

... 未知专业的成员, 一个嵌套类或枚举,它是当前实例化的依赖成员, ...

所以第 9 段中的第一个项目符号涵盖了typename T::Inner 的情况。那是一个依赖类型。

同时,您的案例已被第二个项目符号覆盖。 Outer::Inner 是在Outer 的当前实例化中找到的名称,而且它在Outer 本身中找到,而不是在基类中。这使它成为当前实例化的依赖成员。此名称指的是嵌套类。这意味着第二个项目符号中的所有条件都适用,从而使 Outer::Inner 也成为依赖类型!

由于在这两种情况下我们都有自己的依赖类型,编译器应该将它们平等地视为依赖类型。我的结论是 GCC 和 MSVC 是对的。

【讨论】:

Bug reported。谢谢。

以上是关于Clang 是不是正确拒绝仅通过特化定义类模板的嵌套类的代码?的主要内容,如果未能解决你的问题,请参考以下文章

仅针对一个索引的 C++ 方法模板特化

函数模板参数包后跟模板参数和特化

这是为成员函数的 C++ 模板部分特化构造类的好方法吗?

干货C++通过模板特化实现类型萃取实例--实现区分基本类型与自定义类型的memcpy

为另一种模板类型声明模板特化的正确方法是啥?

通过类的模板参数特化成员模板结构