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

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 的地方被认为是不完整的。

然而,似乎有一个 "workaround" 使相同的 class 定义有效:

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; }

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

  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; } 

又一次fails to compile.

我将以 IANALL(不是语言律师)的身份回答你问题的第三部分。

该代码无效的原因与在函数声明之前使用该函数无效的原因相同 - 即使编译器可以通过在同一翻译单元中进一步向下推算出该函数应该是什么。从某种意义上说,这些情况也很相似,如果您碰巧只有一个没有定义的声明,这对编译器来说已经足够了,而在这里您碰巧在实例化之前有一个模板定义。

所以重点是:语言标准要求编译器不会提前为您当您想要定义某些东西时(并且 class 模板不是class).

的定义

我认为这个例子是

明确允许的

17.6.1.2 Member classes of class templates [temp.mem.class]

1 A member class of a class template may be defined outside the class template definition in which it is declared. [Note: The member class must be defined before its first use that requires an instantiation (17.8.1) For example,

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

—end note ]

这也应该 work 没问题:

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;
}

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

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

Non-static data members shall not have incomplete types.

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

For a class template specialization, a class member template specialization, or a specialization for a class member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization, if the context from which the specialization is referenced depends on a template parameter, and if the specialization is not instantiated previous to the instantiation of the enclosing template, the point of instantiation is immediately before the point of instantiation of the enclosing template. Otherwise, the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

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

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

从整体上看,这一切意味着什么?我不确定。

有关已接受答案的更多详细信息

我不确定接受的答案是否是正确的解释,但这是目前最合理的解释。从该答案推断,以下是我最初问题的答案:

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

更多解释

似乎 正在发生什么

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

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

A specialization is a class, function, or class member that is either instantiated or explicitly specialized.

所以在使用模板的原始实现中对 foo{}.x.value 的调用符合专业化要求(这对我来说是新事物)。

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

具有显式特化的版本无法编译,因为看起来是这样的:

if the context from which the specialization is referenced depends on a template parameter

不再成立,因此来自 [temp.point]/4 的规则不适用。