【问题标题】:Compile time evaluation of literal type members文字类型成员的编译时评估
【发布时间】:2016-11-29 19:51:36
【问题描述】:

假设我们有两个字面量类型 AB 定义如下。文字类型 B 可以在编译时从文字类型 A 初始化。第三个非文字类型C包含文字类型B作为成员。问题是,如果我们在编译时初始化 a 并将其传递给 C 的构造函数,那么 b 是否也在编译时初始化?由于 C 是非文字类型,其成员 v 在编译时不会被初始化。但是,b 是否发生了部分编译时初始化?如果是这种情况,如果在 C 中使用 constexpr 成员函数,例如 f,是否会在编译时进行评估?显然我们不能在编译时在结构 C 之外使用这样的函数,因为我们不能将 C 的对象声明为 constexpr

struct A {
    constexpr A(int num): num{num} {}
    int num;
};

struct B {
    constexpr B(A a): num{a.num} {}
    int num;
};

template<typename T>
struct C {
    C(A a, std::initializer_list<T> data): b{a}, v{data} {}

    constexpr int f() const {
        return b.num + 42;
    }

    B b;
    std::vector<T> v;
};

int main() {
     constexpr A a {4};
     C<int> c {a, {5}};
}

【问题讨论】:

  • 标准没有定义“编译时间”。你只能问某事是否是一个常量表达式。

标签: c++ c++11


【解决方案1】:

您对f 的声明是非法的,因为它是非文字类的非static constexpr 成员。 GCC 6.1 给出以下错误:

error: enclosing class of constexpr non-static member function 'int C::f() const' is not a literal type
note: 'C' is not literal because:
note: 'C' has a non-trivial destructor

因此,询问CB 数据成员是否在编译时求值是没有意义的,因为无法观察它何时求值。

【讨论】:

  • 这是真的!谢谢你指出这一点。如果 C 是模板化的,则代码在 g++ 5.3.0 上编译时不会出错。上面的玩具示例是受模板类启发的,我在添加 f() 后忘记编译了。我编辑了这个问题,现在应该可以正常编译了。
  • @TeodorNikolov 但是现在你没有合法的实例化,严格来说,因为不可能 T 可以使它成为一个文字类。
  • @SebastianRedl 不完全;我认为可以为用户提供的T 设想std::vectorstd::allocator 的专业化,这样前者的析构函数可以是微不足道的,同时仍符合[namespace.std]/2。另外,我不确定 [temp.res]/8 中的“有效”是否旨在涵盖 [namespace.std]/2 违规行为。
  • 我很确定用户提供的向量专业化是 UB,但不完全确定。
  • @SebastianRedl 不,它在 [container] 中没有被禁止,所以如果它满足要求就可以了:“只有当声明依赖于用户定义的类型和特化符合原始模板的标准库要求,并且没有明确禁止。” ([namespace.std]/1,而不是我上面写错的 [namespace.std]/2。)
【解决方案2】:

作为一般规则,除非在编译时需要最终结果,否则对 constexpr 函数的任何调用可能,但不必必须在编译时进行评估.由于C 的构造函数在编译时不需要它的B 参数,因此它是否要尽早评估转换或生成代码取决于编译器。

一般来说,我相信您会发现编译器在任何大于零的优化级别都会内联并评估构造函数,只需将 4 分配给 C 实例内的适当位置并直接调用向量构造函数,如果它不只是内联它并展开初始化循环,基本上会以看起来像这样的代码结束:

int main() {
  C c = UNINITIALIZED;
  c.b.num = 4;
  c.v._buffer = operator new(sizeof(int) * _MINIMUM_ELEMENTS);
  c.v._capacity = _MINIMUM_ELEMENTS;
  c.v._size = 1;
  c.v._buffer[0] = 5;
}

找出答案的唯一方法是编译代码并让编译器发出它生成的程序集。

【讨论】:

    猜你喜欢
    • 2017-12-22
    • 1970-01-01
    • 2011-09-27
    • 1970-01-01
    • 2022-07-11
    • 2011-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多