【问题标题】:Initializing a static constexpr from an incomplete type because of a template base class由于模板基类,从不完整类型初始化静态 constexpr
【发布时间】:2014-01-08 23:33:42
【问题描述】:

我有一个模板基类,期望子类将自己作为模板参数传递。

看起来有点像这样:

template<typename T>
struct Base {
    constexpr Base(int x) : m_x(x) {}
private:
    int m_x;
};

struct Derived : public Base<Derived>
{
    static const Derived LIFE;
    constexpr Derived(int x) : Base(x) {}
};

const Derived Derived::LIFE = Derived(42);

编译并按预期工作。但现在我想让 Derived::LIFE 成为 constexpr。这甚至可能吗?

我不能只是将它的 const 限定符更改为 constexpr,因为 constexpr 需要在其声明中进行初始化:

test.cpp:10:28: error: constexpr static data member ‘LIFE’ must have an initializer
   static constexpr Derived LIFE;

我不能在那里初始化它,因为 Derived 是一个不完整的类型:

test.cpp:10:45: error: invalid use of incomplete type ‘struct Derived’
   static constexpr Derived LIFE = Derived(42);

我意识到如果 Derived 是一个完整的类型,这个问题就会消失,但在这种特殊情况下,由于与这个问题无关的原因,我非常依赖自引用模板基类。

如果我正确理解了this answer 中的最后一段,听起来至少有一些关于在未来某个时候改变不完整类型的处理方式的讨论,但这对我现在没有帮助。

有人知道在我上面的代码中延迟初始化 LIFE 的某种技巧吗?

【问题讨论】:

  • “我有一个模板基类,期望子类将自己作为模板参数传递。”它被称为Curiously Recurring Template Pattern
  • 谢谢@dyp:我一直想要一个模式的名字。所以我认为显而易见(但丑陋)的解决方案是将常量移出其类。我怀疑在我的情况下重组事物以使类型完整是不切实际的。
  • 嗯,在阅读了链接的答案和标准后,我仍然认为我的第二条(现已删除)评论是正确的:因为 DerivedDerived 的“范围”内的不完整类型(名称查找推迟到类定义之后),您不能将其类型的 constexpr 对象创建为静态数据成员。但是,类的成员函数的定义不在其范围内。所以你应该可以使用static constexpr Derived get_LIFE() { return {42}; }

标签: c++ templates c++11 constexpr


【解决方案1】:

您可以简单地将constexpr 添加到LIFE 的定义中:

constexpr Derived Derived::LIFE = Derived(42);

直到最近 GCC 有一个错误,它拒绝了这个;您需要使用 Clang 或 GCC 4.9。

【讨论】:

  • 那么即使constexpr 不是声明的一部分,你能不能把它放在定义上?看起来很有趣,具体在哪里?
  • @litb 没有规定说你不能。 7.1.5/1 说“constexpr 说明符应仅应用于变量的定义 [...]。如果函数或函数模板的任何声明具有 constexpr 说明符,则其所有声明应包含constexpr 说明符。”所以变量声明不能有说明符,但变量重新声明(与函数不同)不需要匹配 constexpr 说明符。
  • 我使用 GCC (ideone)、clang (rextester) 和 MSVC 2017 对此进行了测试,虽然 IntelliSense 不太关心它,但这在所有三个编译器上都完全符合预期.我通过将std::array 的大小传递给static constexpr 成员来访问它们,以确保它们作为编译时常量表达式被访问。
  • 这不适用于 ICC:gcc.godbolt.org/z/aquzGx 但仍然 +1
【解决方案2】:

我想你应该使用lazy initialization。实际上Derived 仍然是不完整的类型;因为编译器还不知道它的大小。

所以代码应该是:

struct Derived : public Base<Derived>
{
    constexpr Derived(int x) : Base(x) {}

    static constexpr Derived getLIFE()
    {
        return Derived(34);
    }
};

编辑: 使用此 sn-p 可以重现相同的 incomplete type 行为:

struct MyStruct
{
    static constexpr int x = sizeof(MyStruct);
};

【讨论】:

  • 啊,是的 - 效果很好。非常感谢你和@dyp。
  • 我也有这个想法,但它的作用与 OP 的方法不同,即每次调用它都会产生一个 new 对象。再说一次,我想当你有一个常量表达式时,对象标识的概念变得有点毫无意义。
猜你喜欢
  • 2021-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-17
相关资源
最近更新 更多