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

实例化一个不完整类型的类模板时,如果该类型是后来定义的,那么它的格式是否错误?

  •  11
  • geza  · 技术社区  · 6 年前

    这个代码肯定是格式错误的,因为 Foo

    template <typename T>
    struct Foo {
        int a;
    };
    
    Foo<int> x = { 42 };
    
    template <>
    struct Foo<int> {
        const char *a;
    };
    
    Foo<int> x = { "bar" };
    

    它是不成形的,因为 standard 我强调:

    函数模板、成员函数模板或类模板的成员函数或静态数据成员的专用化可以在翻译单元内具有多个实例化点,并且除了上述实例化点之外,对于在翻译单元内具有实例化点的任何此类专门化,翻译单元的末端也被认为是实例化点。类模板的专门化在翻译单元中最多有一个实例化点。任何模板的专门化都可以在多个翻译单元中有实例化点。

    现在,是

    struct A;
    
    template <typename> class Foo { };
    
    Foo<A> foo; // note A is incomplete here
    
    struct A {};
    

    病态会改变吗

    struct A;
    
    template <typename T>
    struct Foo {
        Foo() {
            new T;
        }
    };
    
    Foo<A> foo; // note A is incomplete here
    
    struct A {};
    

    question .

    注意,这不是重复的。这个问题是关于代码为什么要编译,这个问题是关于它是否格式错误。它们不同,因为格式错误的程序不一定是非编译程序。


    new T 编译,而这个例子( T

    struct A;
    
    template <typename T>
    struct Foo {
        T t;
    };
    
    Foo<A> foo; // note A is incomplete here
    
    struct A {};
    

    3 回复  |  直到 6 年前
        1
  •  4
  •   Community Reversed Engineer    4 年前

    假设我们只有一个翻译单元, [temp.point] 排除你的报价可能是形式不良的根源

    类模板的专门化在翻译单元中最多有一个实例化点。

    相反,第一个片段的问题是 [temp.expl.spec]

    如果一个模板、一个成员模板或一个类模板的一个成员被显式地专门化,那么在第一次使用该专门化之前,应该声明该专门化,该专门化将导致隐式实例化发生,在发生这种使用的每个翻译单元中;不需要诊断。

    第二个代码段格式良好,不要求模板参数具有完整的类型。

    第三个片段格式错误, new T 要求 T 是一个完整的类型。这里需要注意的一点是,构造函数的定义在 Foo<A> foo;

    struct A;
    
    template <typename T>
    struct Foo {
        Foo() {
            new T;
        }
    };
    
    using FooA = Foo<A>;
    
    struct A {};
    

    然后是构造函数的定义 实例化,因此格式良好。 [temp.inst]

    第四个代码段的格式不正确,因为成员需要具有完整的类型。 [class.mem]

    非静态数据成员的类型不应是不完整的类型[…]

        2
  •  7
  •   Yakk - Adam Nevraumont    6 年前
    struct A;
    template <typename> class Foo { };
    Foo<A> foo; // note A is incomplete here
    struct A {};
    

    Foo<A> 只取决于 A 不是它的完整类型。

    所以这是格式良好的;然而,这类东西仍然可以在您测试的每个编译器中中断(变成格式不良)而编译。

    is_complete . 然后我们这样做:

    struct A;
    template <class T> class Foo {
      enum{ value = is_complete<T>::value };
    };
    Foo<A> foo; // note A is incomplete here
    struct A {};
    

    我们没事,尽管如此:

    因为该子句不适用于模板类。在这里,模板类的唯一实例化是好的。

    现在,如果在另一个文件中有:

    struct A {};
    Foo<A> foo2;
    

    你的程序格式不正确。

    但是,在一个文件案例中:

    struct A;
    template <class T> class Foo {
      enum{ value = is_complete<T>::value };
    };
    Foo<A> foo; // note A is incomplete here
    struct A {};
    Foo<A> foo2; // ill-formed
    

    你的密码很好。有一个实例化点 Foo<A> 在给定的编译单元中;第二个是对第一个实例化点的引用。

    一个和两个文件VrSOIN几乎肯定会编译在C++编译器中,没有错误或警告。

    有些编译器甚至将模板实例化从一个编译单元存储到另一个编译单元; Foo<A> 会有一个 ::value 就是这样 false foo2 A Foo<A> 它的方法将被标记为inline(并且是不同的),类的大小可能不一致,并且您将得到一系列格式错误的程序问题。


    最后,请注意 std 要求他们的模板参数在C++的旧版本中完成(包括 :17.6.4.8其他功能(…)2。在以下情况下,效果是未定义的:(…)尤其是-如果在实例化模板组件时将不完整类型(3.9)用作模板参数,除非该组件特别允许-从boost不完整容器文档复制)。具体来说, std::vector<T> T 要完整。

    changed for std::vector :

    [vector.概述]/3

    如果分配器满足分配器完整性要求17.6.3.5.1,则在实例化向量时可以使用不完整类型T。T应在向量的结果专门化的任何成员被引用之前完成。

    ,大多数实现 对不完整的 T型 直到您尝试使用一个方法(包括它的许多构造函数或析构函数),但是标准声明 必须

    这实际上妨碍了一些无用的代码,比如函数类型返回自己类型的向量 1 . Boost


    template <typename T>
    struct Foo {
      Foo() {
        new T;
      }
    };
    

    身体 Foo<T>::Foo() 仅在“调用时”实例化。所以呢 T型 Foo::Foo() 被称为。

    Foo<A> foo;
    

    ^^将无法编译一个不完整的 A

    using foo_t = Foo<A>;
    

    ^^将编译,不会引起任何问题。

    using foo_t = Foo<A>;
    struct A {};
    foo_t foo;
    

    foo_t::foo_t 当我们试图构造 foo_t ,所有定义都匹配。


    1

        3
  •  1
  •   YSC    6 年前

    幸运的是,这是一个很好的定义。出于完全相同的原因,这是一个明确的定义:

    struct A;
    
    class Foo { A* value; };
    
    Foo foo; // note A is incomplete here
    
    struct A {};
    

    这是错误的:

    struct A;
    
    template <class T> class Foo { T value; }; // error: 'Foo<T>::value' has incomplete type
    
    Foo<A> foo; // note A is incomplete here
    
    struct A {};