何时以及为什么要使用constexpr静态?

voi*_*ter 22 c++ static constexpr

作为免责声明,我在询问之前已对此进行过研究.我发现了一个类似的问题,但答案有点"稻草人",并没有真正回答我个人的问题.我也提到了我方便的cppreference页面,但这并没有提供对事物的非常"愚蠢"的解释.

基本上我还在继续constexpr,但目前我的理解是它需要在编译时评估表达式.由于它们可能仅在编译时存在,因此它们在运行时不会真正具有内存地址.因此,当我看到人们使用时static constexpr(例如在类中)static会让我感到困惑...... 因为这只对运行时上下文有用,所以在这里会是多余的.

我在" constexpr除了编译时表达式之外不允许任何东西"声明中看到了矛盾(特别是在SO).但是,Bjarne Stroustrup的页面中的一篇文章在各种示例中解释了实际上constexpr 确实需要在编译时对表达式进行评估.如果不是,则应生成编译器错误.

我之前的段落似乎有点偏离主题,但它是理解为什么static可以或应该使用的必要基线constexpr.不幸的是,该基线有很多矛盾的信息浮出水面.

任何人都可以帮助我将所有这些信息整合到纯粹的事实中,并提供有意义的示例和概念吗?基本上在了解constexpr真实行为的基础上,您为什么要使用static它?通过什么范围/场景确实static constexpr有意义,如果它们可以一起使用?

dyp*_*dyp 16

constexpr变量不是编译时值

值是不可变的并且不占用存储(它没有地址),但是声明为的对象constexpr可以是可变的并且占用存储(在as-if规则下).

可变性

声明为大多数的对象constexpr是不可变的,但是可以定义一个constexpr(部分)可变的对象,如下所示:

struct S {
    mutable int m;
};

int main() {
    constexpr S s{42};
    int arr[s.m];       // error: s.m is not a constant expression
    s.m = 21;           // ok, assigning to a mutable member of a const object
}
Run Code Online (Sandbox Code Playgroud)

存储

在as-if规则下,编译器可以选择分配任何存储来存储声明为的对象的值constexpr.同样,它可以对非constexpr变量进行此类优化.但是,请考虑我们需要将对象的地址传递给未内联的函数的情况; 例如:

struct data {
    int i;
    double d;
    // some more members
};
int my_algorithm(data const*, int);

int main() {
    constexpr data precomputed = /*...*/;
    int const i = /*run-time value*/;
    my_algorithm(&precomputed, i);
}
Run Code Online (Sandbox Code Playgroud)

这里的编译器需要为其分配存储空间precomputed,以便将其地址传递给某些非内联函数.这是可能的编译器分配的存储的precomputedi连续; 可以想象这可能影响性能的情况(见下文).

Standardese

变量是对象或引用[basic]/6.让我们关注对象.

类似的声明constexpr int a = 42;是一个简单的声明 ; 它由decl-specifier-seq init-declarator-list组成 ;

从[dcl.dcl]/9开始,我们可以得出结论(但并非严格地说)这样的声明声明了一个对象.具体来说,我们可以(严格地)得出结论,它是一个对象声明,但这包括引用的声明.另请参阅关于我们是否可以使用类型变量void的讨论.

所述constexpr在对象的声明意味着该对象的类型是const [dcl.constexpr]/9.对象是存储区域[intro.object]/1.我们可以从[intro.object]/6和[intro.memory] ​​/ 1推断出每个对象都有一个地址.请注意,我们可能无法直接获取此地址,例如,如果通过prvalue引用对象.(甚至有prvalues不是对象,例如文字42.)两个不同的完整对象必须具有不同的地址[intro.object]/6.

从这一点来看,我们可以得出结论,声明为的对象constexpr必须具有相对于任何其他(完整)对象的唯一地址.

此外,我们可以得出结论,声明constexpr int a = 42;声明了一个具有唯一地址的对象.

静态和constexpr

恕我直言唯一有趣的问题是"每个功能static",即la

void foo() {
    static constexpr int i = 42;
}
Run Code Online (Sandbox Code Playgroud)

据我所知 - 但这似乎还不完全清楚 - 编译器可能会constexpr在运行时计算变量的初始化程序.但这似乎是病态的; 我们假设它没有这样做,即它在编译时预先计算初始化程序.

static constexpr局部变量的初始化在静态初始化期间完成,必须在任何动态初始化[basic.start.init]/2之前执行.虽然不能保证,但我们可以假设这不会产生运行时/加载时间成本.此外,由于常量初始化没有并发问题,我认为我们可以安全地假设这不需要线程安全的运行时检查static变量是否已经初始化.(调查clang和gcc的来源应该对这些问题有所了解.)

对于非静态局部变量的初始化,有些情况下编译器在常量初始化期间无法初始化变量:

void non_inlined_function(int const*);

void recurse(int const i) {
    constexpr int c = 42;
    // a different address is guaranteed for `c` for each recursion step
    non_inlined_function(&c);
    if(i > 0) recurse(i-1);
}

int main() {
    int i;
    std::cin >> i;
    recurse(i);
}
Run Code Online (Sandbox Code Playgroud)

结论

看起来,static constexpr在某些极端情况下,我们可以从变量的静态存储持续时间中受益.但是,我们可能会丢失此局部变量的局部性,如本答案的"存储"部分所示.直到我看到一个基准测试表明这是一个真实的效果,我将假设这是不相关的.

如果只有这两种效应staticconstexpr对象,我会用static每默认:我们通常不需要唯一地址为我们的保障constexpr对象.

对于可变constexpr对象(具有mutable成员的类类型),本地static和非静态constexpr对象之间显然存在不同的语义.类似地,如果地址本身的值是相关的(例如,对于散列图查找).


dyp*_*dyp 6

仅举例.社区维基.

static == per-function(静态存储持续时间)

声明为constexpr具有任何其他对象的地址的对象.如果由于某种原因,使用了对象的地址,则编译器可能必须为其分配存储:

constexpr int expensive_computation(int n); // defined elsewhere

void foo(int const p = 3) {
    constexpr static int bar = expensive_computation(42);
    std::cout << static_cast<void const*>(&bar) << "\n";
    if(p) foo(p-1);
}
Run Code Online (Sandbox Code Playgroud)

对于所有调用,变量的地址都是相同的; 每个函数调用都不需要堆栈空间.相比于:

void foo(int const p = 3) {
    constexpr int bar = expensive_computation(42);
    std::cout << static_cast<void const*>(&bar) << "\n";
    if(p) foo(p-1);
}
Run Code Online (Sandbox Code Playgroud)

这里,每个(递归)调用的地址都不同foo.

这很重要,例如,如果对象很大(例如数组),并且我们需要在需要常量表达式的上下文中使用它(需要编译时常量),并且我们需要获取其地址.

请注意,由于地址必须不同,因此可能会在运行时初始化对象 ; 例如,如果递归深度取决于运行时参数.初始化程序仍然可以预先计算,但结果可能必须复制到每个递归步骤的新内存区域.在这种情况下,constexpr只保证可以在编译时评估初始化程序,并且可以在编译时为该类型的变量执行初始化.

static ==每班

template<int N>
struct foo
{
    static constexpr int n = N;
};
Run Code Online (Sandbox Code Playgroud)

与往常一样:声明的每个模板特(实例)的变量foo,例如foo<1>,foo<42>,foo<1729>.如果要公开非类型模板参数,可以使用例如静态数据成员.它可以constexpr使其他人可以从编译时已知的值中受益.

static ==内部联系

// namespace-scope
static constexpr int x = 42;
Run Code Online (Sandbox Code Playgroud)

非常多余; constexpr变量每个默认值都有内部链接.static在这种情况下,我认为目前没有任何理由可以使用.

  • 也许不是一个非常有力的理由,但在 C11(不是 C++)中隐含的 `static` 已被弃用(第 6.11.2 节)。一般而言,由于`static` 对象确实具有每个 TU 不同地址的惊人特性,所以明确说明它是个好主意。 (2认同)