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

时间:2018-05-06 17:40:10

标签: c++ c++11 templates language-lawyer forward-declaration

以下是无效代码:

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

works包含所有主要编译器。 我有三个问题:

  1. 这确实是有效的C ++代码,还是只是编译器的一个怪癖?
  2. 如果它是有效代码,C ++标准中是否有处理此异常的段落?
  3. 如果它是有效代码,为什么第一个版本(没有template)被视为无效?如果编译器可以找出第二个选项,我就不会看到为什么它无法弄清楚第一个选项的原因。
  4. 如果我为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

4 个答案:

答案 0 :(得分:6)

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

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

  

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

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

  

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

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

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

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

答案 1 :(得分:5)

我认为

明确允许这个例子
  

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
     

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

答案 2 :(得分:0)

关于接受答案的更多细节

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

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

    似乎正在发生什么

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

答案 3 :(得分:-3)

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

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

所以重点是:语言标准要求编译器不要向前看,当你想要定义某些东西时(并且类模板不是类的定义)。