繁体   English   中英

类中不允许使用不完整类型,但允许在类模板中使用

[英]Incomplete type is not allowed in a class, but is allowed in a class template

以下是无效代码:

struct foo {
    struct bar;
    bar x;        // error: field x has incomplete type
    struct bar{ int value{42}; };
};

int main() { return foo{}.x.value; }

这很清楚,因为foo::bar在定义foo::x被认为是不完整的。

但是,似乎有一个“解决方法”使相同的类定义有效:

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

int main() { return foo{}.x.value; }

适用于所有主要编译器。 我有三个问题:

  1. 这确实是有效的C ++代码,还是只是编译器的一个怪癖?
  2. 如果它是有效的代码,C ++标准中是否有一个处理此异常的段落?
  3. 如果它是有效代码,为什么第一个版本(没有template )被认为是无效的? 如果编译器可以找出第二个选项,我没有看到为什么它无法找出第一个选项的原因。

如果我为void添加显式特化:

template <typename = void>
struct foo_impl {};

template<>
struct foo_impl<void> {
    struct bar;
    bar x;        // error: field has incomplete type
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

int main() { return foo{}.x.value; } 

它再次无法编译

真正的答案可能是¯\\ _(ツ)_ /¯,但它可能目前还可以,因为模板很神奇,但在某些其他核心问题解决方案之前可能更明确地不行。

首先,主要问题当然是[class.mem] / 14

非静态数据成员不应具有不完整的类型。

这就是您的非模板示例格式错误的原因。 但是,根据[temp.point] / 4

对于类模板特化,类成员模板特化或类模板的类成员的特化,如果特化是隐式实例化的,因为它是从另一个模板特化中引用的,如果引用特化的上下文取决于在模板参数上,并且如果在封闭模板的实例化之前未实例化特化,则实例化的点紧接在封闭模板的实例化点之前 否则,这种特化的实例化点紧接在引用特化的命名空间范围声明或定义之前。

这表明foo_impl<void>::bar foo_impl<void> 之前被实例化,因此它在实例化bar类型的非静态数据成员时完成。 所以也许没关系。

但是 ,核心语言问题16262335处理的是关于完整性和模板的不完全相同但仍然非常相似的问题,并且都指向希望使模板案例与非模板案例更加一致。

从整体上看,所有这些意味着什么? 我不确定。

我认为这个例子是明确允许的

17.6.1.2类模板的成员类[temp.mem.class]

1类模板的成员类可以在声明它的类模板定义之外定义。 [注意:成员类必须在首次使用之前定义,需要实例化(17.8.1)例如,

 template<class T> struct A { class B; }; A<int>::B* b1; // OK: requires A to be defined but not A::B template<class T> class A<T>::B { }; A<int>::B b2; // OK: requires A::B to be defined 

- 尾注]

这应该也可以正常工作

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
};

template<typename T>
struct foo_impl<T>::bar{ int value{42}; };

using foo = foo_impl<>;

int main()
{
    return foo{}.x.value;
}

关于接受的答案的更多细节

我不确定接受的答案是正确的解释,但它现在是最合理的解释。 根据该答案推断,以下是我原始问题的答案:

  1. 这确实是有效的C ++代码,还是只是编译器的一个怪癖? [ 这是有效的代码。 ]
  2. 如果它是有效的代码,C ++标准中是否有一个处理此异常的段落? [ [temp.point] / 4 ]
  3. 如果它是有效代码,为什么第一个版本(没有template )被认为是无效的? 如果编译器可以找出第二个选项,我没有看到为什么它无法找出第一个选项的原因。 [ 因为C ++很奇怪 - 它处理类模板的方式与类不同(你可能已经猜到了这个)。 ]

更多解释

似乎正在发生什么

main实例化foo{}时,编译器为foo_impl<void>实例化(隐式)特化。 此专门化引用第4行( bar x; )上的foo_impl<void>::bar 上下文在模板定义中,因此它依赖于模板参数,并且特殊化foo_impl<void>::bar显然以前没有实例化,因此[temp.point] / 4的所有前提条件都已满足,并且编译器生成以下中间(伪)代码:

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

// implicit specialization of foo_impl<void>::bar, [temp.point]/4
$ struct foo_impl<void>::bar {
$     int value{42};
$ };
// implicit specialization of foo_impl<void> 
$ struct foo_impl<void> {
$     struct bar;
$     bar x;   // bar is not incomplete here
$ };
int main() { return foo{}.x.value; }

关于专业化

根据[temp.spec] / 4

专门化是实例化或显式专用的类,函数或类成员。

所以在带有模板的原始实现中对foo{}.x.value的调用有资格作为一种特殊化(这对我来说是新的)。

关于具有显式专业化的版本

具有显式特化的版本不会编译,因为它似乎:

如果引用特化的上下文取决于模板参数

不再持有,因此[temp.point] / 4的规则不适用。

我将回答你问题的第三部分 - 作为IANALL(不是语言律师)。

代码是无效的,因为在声明它之前使用函数是无效的 - 即使编译器可以通过在同一个翻译单元中进一步向下找出函数应该是什么。 并且案例也是相似的,如果你碰巧只有一个没有定义的声明,这对编译器来说已经足够了,而在这里你碰巧在实例化之前有了一个模板定义。

所以重点是: 语言标准要求编译器在您想要定义某些内容时不会向前看 (并且类模板不是类的定义)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM