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

时间:2021-07-22 21:39:24

The following is invalid code:

以下是无效代码:

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

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

This is quite clear, as foo::bar is considered incomplete at the point where foo::x is defined.

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

However, there seems to be a "workaround" which makes the same class definition valid:

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

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

This works with all major compilers. I have three questions about this:

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

  1. Is this indeed valid C++ code, or just a quirk of the compilers?
  2. 这确实是有效的C ++代码,还是只是编译器的一个怪癖?

  3. If it is valid code, is there a paragraph in the C++ standard that deals with this exception?
  4. 如果它是有效的代码,C ++标准中是否有一个处理此异常的段落?

  5. If it is valid code, why is the first version (without template) considered invalid? If the compiler can figure out the second option, I don't see a reason why it wouldn't be able to figure out the first one.
  6. 如果它是有效代码,为什么第一个版本(没有模板)被认为是无效的?如果编译器可以找出第二个选项,我没有看到为什么它无法弄清楚第一个选项的原因。


If I add an explicit specialization for void:

如果我为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; } 

It once again fails to compile.

它再次无法编译。

4 个解决方案

#1


6  

The real answer might be ¯\_(ツ)_/¯, but it's probably currently okay because templates are magical, but it may be more explicitly not okay pending some other core issue resolutions.

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

First, the main problem of course is [class.mem]/14:

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

Non-static data members shall not have incomplete types.

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

This is why your non-template example is ill-formed. However, according to [temp.point]/4:

这就是您的非模板示例格式错误的原因。但是,根据[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.

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

Which suggests that foo_impl<void>::bar is instantiated before foo_impl<void>, and hence it's complete at the point where the non-static data member of type bar is instantiated. So maybe it's okay.

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

However, core language issues 1626 and 2335 deal with not-exactly-the-same-but-still-quite-similar issues regarding completeness and templates, and both point to desiring to make the template case more consistent with the non-template case.

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

What does all of this mean when viewed as a whole? I'm not sure.

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

#2


5  

I think this example is explicitly allowed by

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

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

17.6.1.2类模板的成员类[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,

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

—end note ]

- 尾注]

This should work fine too:

这应该也可以正常工作:

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

#3


0  

More details about the accepted answer

I am not sure that the accepted answer is the correct explanation, but it is the most plausible one for now. Extrapolating from that answer, here are the aswers to my original questions:

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

  1. Is this indeed valid C++ code, or just a quirk of the compilers? [ It is valid code. ]
  2. 这确实是有效的C ++代码,还是只是编译器的一个怪癖? [这是有效的代码。 ]

  3. If it is valid code, is there a paragraph in the C++ standard that deals with this exception? [ [temp.point]/4 ]
  4. 如果它是有效的代码,C ++标准中是否有一个处理此异常的段落? [[temp.point] / 4]

  5. If it is valid code, why is the first version (without template) considered invalid? If the compiler can figure out the second option, I don't see a reason why it wouldn't be able to figure out the first one. [ Because C++ is weird - it handles class templates differently than classes (you could have probably guessed this one). ]
  6. 如果它是有效代码,为什么第一个版本(没有模板)被认为是无效的?如果编译器可以找出第二个选项,我没有看到为什么它无法弄清楚第一个选项的原因。 [因为C ++很奇怪 - 它处理类模板的方式与类不同(你可能已经猜到了这个)。 ]

Some more explanations

What seems to be happening

When instantiating foo{} in main the compiler instantiates an (implicit) specialization for foo_impl<void>. This specialization references foo_impl<void>::bar on line 4 (bar x;). The context is within a template definition so it depends on a template parameter, and the specialization foo_impl<void>::bar is obviously not previously instantiated, so all the preconditions for [temp.point]/4 are fulfilled, and the compiler generates the following intermediate (pseudo)code:

在main中实例化foo {}时,编译器为foo_impl 实例化(隐式)特化。此专门化引用第4行(bar x;)上的foo_impl :: bar。上下文在模板定义中,因此它依赖于模板参数,并且特殊化foo_impl :: 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; }

About specialization

As per [temp.spec]/4:

根据[temp.spec] / 4:

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

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

so the call to foo{}.x.value in the original implementation with templates qualifies as a specialization (this was something new to me).

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

About the version with explicit specialization

The version with explicit specialization does not compile as it seems that:

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

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

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

no longer holds, so the rule from [temp.point]/4 does not apply.

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

#4


-3  

I'll answer the third part of your question - as IANALL (not a language lawyer).

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

The code is invalid for the same reason it's invalid to use a function before it has been declared - even though the compiler can figure out what the function's supposed to be by going further down in the same translation unit. And the cases are similar also in the sense that if you happen to have just a declaration with no definition, that's good enough for the compiler, while here you happen to have a template definition before the instantiation.

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

So the point is: The language standard mandates that the compiler does not look ahead for you when you want to define something (and a class template is not a definition of a class).

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

#1


6  

The real answer might be ¯\_(ツ)_/¯, but it's probably currently okay because templates are magical, but it may be more explicitly not okay pending some other core issue resolutions.

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

First, the main problem of course is [class.mem]/14:

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

Non-static data members shall not have incomplete types.

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

This is why your non-template example is ill-formed. However, according to [temp.point]/4:

这就是您的非模板示例格式错误的原因。但是,根据[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.

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

Which suggests that foo_impl<void>::bar is instantiated before foo_impl<void>, and hence it's complete at the point where the non-static data member of type bar is instantiated. So maybe it's okay.

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

However, core language issues 1626 and 2335 deal with not-exactly-the-same-but-still-quite-similar issues regarding completeness and templates, and both point to desiring to make the template case more consistent with the non-template case.

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

What does all of this mean when viewed as a whole? I'm not sure.

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

#2


5  

I think this example is explicitly allowed by

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

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

17.6.1.2类模板的成员类[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,

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

—end note ]

- 尾注]

This should work fine too:

这应该也可以正常工作:

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

#3


0  

More details about the accepted answer

I am not sure that the accepted answer is the correct explanation, but it is the most plausible one for now. Extrapolating from that answer, here are the aswers to my original questions:

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

  1. Is this indeed valid C++ code, or just a quirk of the compilers? [ It is valid code. ]
  2. 这确实是有效的C ++代码,还是只是编译器的一个怪癖? [这是有效的代码。 ]

  3. If it is valid code, is there a paragraph in the C++ standard that deals with this exception? [ [temp.point]/4 ]
  4. 如果它是有效的代码,C ++标准中是否有一个处理此异常的段落? [[temp.point] / 4]

  5. If it is valid code, why is the first version (without template) considered invalid? If the compiler can figure out the second option, I don't see a reason why it wouldn't be able to figure out the first one. [ Because C++ is weird - it handles class templates differently than classes (you could have probably guessed this one). ]
  6. 如果它是有效代码,为什么第一个版本(没有模板)被认为是无效的?如果编译器可以找出第二个选项,我没有看到为什么它无法弄清楚第一个选项的原因。 [因为C ++很奇怪 - 它处理类模板的方式与类不同(你可能已经猜到了这个)。 ]

Some more explanations

What seems to be happening

When instantiating foo{} in main the compiler instantiates an (implicit) specialization for foo_impl<void>. This specialization references foo_impl<void>::bar on line 4 (bar x;). The context is within a template definition so it depends on a template parameter, and the specialization foo_impl<void>::bar is obviously not previously instantiated, so all the preconditions for [temp.point]/4 are fulfilled, and the compiler generates the following intermediate (pseudo)code:

在main中实例化foo {}时,编译器为foo_impl 实例化(隐式)特化。此专门化引用第4行(bar x;)上的foo_impl :: bar。上下文在模板定义中,因此它依赖于模板参数,并且特殊化foo_impl :: 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; }

About specialization

As per [temp.spec]/4:

根据[temp.spec] / 4:

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

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

so the call to foo{}.x.value in the original implementation with templates qualifies as a specialization (this was something new to me).

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

About the version with explicit specialization

The version with explicit specialization does not compile as it seems that:

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

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

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

no longer holds, so the rule from [temp.point]/4 does not apply.

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

#4


-3  

I'll answer the third part of your question - as IANALL (not a language lawyer).

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

The code is invalid for the same reason it's invalid to use a function before it has been declared - even though the compiler can figure out what the function's supposed to be by going further down in the same translation unit. And the cases are similar also in the sense that if you happen to have just a declaration with no definition, that's good enough for the compiler, while here you happen to have a template definition before the instantiation.

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

So the point is: The language standard mandates that the compiler does not look ahead for you when you want to define something (and a class template is not a definition of a class).

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