代码之家  ›  专栏  ›  技术社区  ›  gflegar

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

  •  20
  • gflegar  · 技术社区  · 6 年前

    以下代码无效:

    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 )是否认为无效?如果编译器能够找到第二个选项,我看不出为什么它不能找到第一个。

    如果我为 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 回复  |  直到 6 年前
        1
  •  6
  •   Barry    6 年前

    真正的答案可能是\ \ \ \(ƒ„)\/u',但目前可能还可以,因为模板很神奇,但在其他一些核心问题解决之前,可能更明确地说不可以。

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

    非静态数据成员的类型不得不完整。

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

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

    这表明 foo_impl<void>::bar 已实例化 之前 foo_impl<void> ,因此在类型为 bar 已实例化。所以也许没关系。

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

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

        2
  •  5
  •   user7860670    6 年前

    我认为这个例子是

    17.6.1.2类模板的成员类[临时成员类]

    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
    

    结束注释]

    这应该 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;
    }
    
        3
  •  0
  •   gflegar    6 年前

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

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

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

    更多解释

    什么 似乎 即将发生

    实例化时 foo{} 在里面 main 编译器实例化(隐式)专门化 foo_impl<void> .此专业化引用 foo_impl<void>::bar 在线4( bar x; )。上下文位于模板定义中,因此它取决于模板参数和专门化 foo\u impl<无效(>::酒吧 显然之前没有实例化,所以 [温度点]/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 在最初的使用模板的实现中,它被称为专门化(这对我来说是新的)。

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

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

    如果引用专门化的上下文依赖于模板参数

    不再适用,因此 [温度点]/4 不适用。

        4
  •  -3
  •   einpoklum    6 年前

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

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

    所以重点是: 语言标准要求编译器不为您考虑未来 当您想要定义某些内容时(类模板不是类的定义)。