首页 文章

如果事后定义了类型,那么实例化一个格式不正确的类模板吗?

提问于
浏览
11

这段代码肯定是格式错误的,因为 Foo 在实例化点之后是专门的:

template <typename T>
struct Foo {
    int a;
};

Foo<int> x = { 42 };

template <>
struct Foo<int> {
    const char *a;
};

Foo<int> x = { "bar" };

由于standard的一部分我强调它是形成不良的:

函数模板,成员函数模板或类模板的成员函数或静态数据成员的特化可以在翻译单元中具有多个实例化点,并且除了上述实例化的点之外,对于任何实例 . 在翻译单元内具有实例化点的这种专门化,翻译单元的末尾也被认为是实例化的点 . 类模板的专门化在翻译单元中最多只有一个实例化点 . 任何模板的专门化可以在多个翻译单元中具有实例化点 . 如果两个不同的实例化点根据单定义规则给出模板特化不同的含义,则程序形成错误,不需要诊断 .

现在, this 代码是不正确的?

struct A;

template <typename> class Foo { };

Foo<A> foo; // note A is incomplete here

struct A {};

如果 Foo 这样声明,那么这种不良形式是否会改变?

struct A;

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

Foo<A> foo; // note A is incomplete here

struct A {};

我问了这个问题,因为在这个问题下讨论了这个问题 .

请注意,这不是重复 . 这个问题是关于代码编译的原因,这个问题是关于它是否是不正确的 . 它们不同,因为不正确的程序不一定是非编译程序 .


注意,使用clang和gcc,我的示例使用 new T 编译,而此示例( T 作为成员)不会:

struct A;

template <typename T>
struct Foo {
    T t;
};

Foo<A> foo; // note A is incomplete here

struct A {};

也许两者都是不正确的,只有最后一种情况才会给出诊断?

3 回答

  • 7

    假设我们只有一个翻译单元,[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]

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

  • 4
    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> 有一个实例化点;第二个是对第一个实例化的引用 .

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

    有些编译器会记住模板实例化,甚至从一个编译单元到另一个编译单元; Foo<A> 即使在创建 foo2 之后也会 ::valuefalse (带有完整的 A ) . 其他人在每个编译单元中都有两个不同的 Foo<A> ;它的方法将被标记为内联(并且是不同的),类的大小可能不同意,并且您将获得错误的程序问题 .


    最后,请注意 std 中的许多类型要求它们的模板参数在旧版本的C中完整(包括c++11:“17.6.4.8其他函数(...)2 . 在以下情况下效果未定义:( ... )特别是 - 如果使用不完整类型(3.9)实例化模板组件时的模板参数,除非特别允许该组件“ - 从boost不完整的容器文档中复制” . 具体而言, std::vector<T> 曾经要求 T 完整 .

    c++17changed for std::vector

    [vector.overview] / 3

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

    现在,甚至在c++17之前,大多数 std::vector<T> 的实现都没有用 T 完成,直到你尝试使用一个方法(包括它的许多构造函数或析构函数),但标准声明 T 必须完整 .

    这实际上妨碍了一些无用的代码,比如有一个函数类型返回它自己的type1的向量 . Boost有一个库来解决这个问题 .


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

    Foo<T>::Foo() 的主体仅被实例化"when called" . 所以 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你能说状态机转换功能吗?

  • 1

    幸运的是,这是明确定义的 . 出于同样的原因,这是明确定义的:

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

相关问题