【问题标题】:static class template member: invalid application of ‘sizeof’ to incomplete type静态类模板成员:“sizeof”对不完整类型的无效应用
【发布时间】:2017-11-23 20:08:38
【问题描述】:

这是我正在尝试创建的池化对象的最小工作示例(显然功能不完整 - 我只是想说明我遇到的问题)

我有一个类模板Storage,其中包含aligned_storage

template<typename T, std::size_t N>
struct Storage
{
    std::aligned_storage_t<sizeof(T), alignof(T)> data[N];
};

然后我有一个基类模板PoolObj,它使用operator new从模板参数T的静态类成员中分配:

template<typename T>
struct PoolObj
{
    static void* operator new(std::size_t size)
    {
        std::cout << "new T\n";
        return &T::pool.data[0];
    }

    static void operator delete(void* p, std::size_t size)
    {
        std::cout << "delete T\n";
    }
};

现在我有一个继承自PoolObj 的类,并且有一个静态Storage 成员pool,所以当我使用new 创建实例时,我将从池中获取存储空间。

struct Foo : PoolObj<Foo>
{
    static Storage<Foo, 10> pool;
};

Storage<Foo, 10> Foo::pool {};

这一切都很好:

int main()
{
    Foo* f = new Foo();
    delete f;
    return 0;
}
$ ./a.out
new T
delete T

不过,现在我正在尝试制作一个启用PoolObj类模板

template<typename T>
struct Bar : PoolObj<Bar<T>>
{
    static Storage<Bar<T>, 10> pool;
};

template<typename T>
Storage<Bar<T>, 10> Bar<T>::pool {};

这不起作用

int main()
{
    Bar<int>* b = new Bar<int>();
    delete b;
    return 0;
}

尝试编译时出现以下错误:

In instantiation of ‘struct Storage<Bar<int>, 10ul>’:
   required from ‘struct Bar<int>’
 error: invalid application of ‘sizeof’ to incomplete type ‘Bar<int>’
  std::aligned_storage_t<sizeof(T), alignof(T)> data[N];
  • 为什么Storage 中的T 对于Foo 来说是完整的,而对于Bar&lt;int&gt; 等来说却是不完整的?
  • 是否有可能实现我在这里希望的设计?

完整示例如下: (and on coliru)

#include <type_traits>
#include <cstddef>

template<typename T, std::size_t N>
struct Storage
{
    std::aligned_storage_t<sizeof(T), alignof(T)> data[N];
};

template<typename T>
struct PoolObj
{
    static void* operator new(std::size_t size)
    {
        return &T::pool.data[0];
    }

    static void operator delete(void* p, std::size_t size)
    {
    }
};

struct Foo : PoolObj<Foo>
{
    static Storage<Foo, 10> pool;
};

Storage<Foo, 10> Foo::pool {};

template<typename T>
struct Bar : PoolObj<Bar<T>>
{
    static Storage<Bar<T>, 10> pool;
};

template<typename T>
Storage<Bar<T>, 10> Bar<T>::pool {};

int main()
{
    Foo* f = new Foo();
    delete f;

    Bar<int>* b = new Bar<int>();
    delete b;

    return 0;
}

编辑:

有趣的是this works fine in clang (coliru)

  • 哪个编译器是正确的?
  • 这是 gcc 中的错误吗?

第二次编辑:

根据 cmets,它也可以在 VS2017 中找到。因此,我想我倾向于 gcc 中的错误?

【问题讨论】:

  • 我猜这是因为您使用 Bar 作为类 Bar 定义的一部分。不确定是否足以使其成为官方答案。但这个错误确实有道理。
  • 补充一点,如果编译器在Bar&lt;T&gt;的定义过程中试图了解静态成员池的大小,那么此时Bar&lt;T&gt;是不完整的。当它会检查std::aligned_storage_t&lt;sizeof(T), alignof(T)&gt; 以了解池的大小时,Bar&lt;T&gt;(在此上下文中为 T)仍然是不完整的,并且取 sizeof 不完整类型是非法的。
  • @OriBS Storage 仅在我们声明 Bar&lt;int&gt; 时为 Bar&lt;T&gt; 实例化。在它被实例化的那一刻,它肯定有完整的类型吗?此外,它在 clang 下工作 - 哪个编译器是正确的?
  • @OriBS:我不会在这里看到为什么类型只会在模板案例中不完整。
  • 顺便说一句,您的示例也可以使用 Visual Studio 2017 进行编译

标签: c++ templates


【解决方案1】:

哪个编译器是正确的?

一般来说,相关的措辞应该是

[temp.inst-2] 类模板特化的隐式实例化导致声明的隐式实例化,但不是定义、默认参数或类成员函数、成员类、作用域成员枚举、静态数据成员、成员模板和朋友的 noexcept 说明符

[temp.inst-3] 除非类模板或成员模板的成员已被显式实例化或显式特化,否则当在要求成员定义存在或成员定义的存在影响程序语义的上下文;特别是,不会发生静态数据成员的初始化(以及任何相关的副作用),除非该静态数据成员本身的使用方式要求该静态数据成员的定义存在

静态成员变量是声明而不是定义,所以clang是对的。

也就是说,两个编译器在决定“实例化声明而不实例化定义”和“需要成员定义存在的上下文或是否存在成员的定义会影响程序的语义”意思是(你可以在 SO 上找到很多极端案例,比如最近 this)。


作为一种解决方法,您可以改用静态引用

template<typename T>
struct Bar : PoolObj<Bar<T>>
{
    static Storage<Bar<T>, 10>&& pool;
};

template<typename T>
Storage<Bar<T>, 10>&& Bar<T>::pool = Storage<Bar<T>, 10>{}; // note, the temporary is lifetime-extended here

这似乎说服了clang和gcc避免实例化aligned_storage(应该是这样)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-09
    • 1970-01-01
    • 1970-01-01
    • 2019-11-25
    • 1970-01-01
    • 1970-01-01
    • 2013-04-05
    • 2015-08-09
    相关资源
    最近更新 更多