什么时候和为什么要使用static和constexpr? [英] When and why would you use static with constexpr?

查看:4651
本文介绍了什么时候和为什么要使用static和constexpr?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为免责声明,我在提出要求之前已经对此进行了研究。我发现一个类似的SO问题,但是答案有点strawman并没有真正回答我个人的问题。我还提到了我的方便的 cppreference页面,但它不提供一个非常dumbed down

As a disclaimer, I have done my research on this before asking. I found a similar SO question but the answer there feels a bit "strawman" and didn't really answer the question for me personally. I've also referred to my handy cppreference page but that doesn't offer a very "dumbed down" explanation of things most times.

基本上我仍然在 constexpr 上加速,但目前我的理解它需要在编译时评估表达式。因为它们只能在编译时存在,所以在运行时它们实际上不会有一个内存地址。所以当我看到人们使用 static constexpr (像在类中,例如)它困惑我... static 因为这只对运行时上下文有用。

Basically I'm still ramping up on constexpr, but at the moment my understanding is that it requires expressions to be evaluated at compile time. Since they may only exist at compile time, they won't really have a memory address at runtime. So when I see people using static constexpr (like in a class, for example) it confuses me... static would be superfluous here since that is only useful for runtime contexts.

我在 constexpr 不允许任何编译时表达式语句(特别是在SO)。但是, Bjarne Stroustrup的页面的文章在各种示例中解释了实际上 constexpr 需要在编译时评估表达式。如果没有,应该产生一个编译错误。

I've seen contradiction in the "constexpr does not allow anything but compile-time expressions" statement (particularly here at SO). However, an article from Bjarne Stroustrup's page explains in various examples that in fact constexpr does require the evaluation of the expression at compile time. If not, a compiler error should be generated.

我的上一段似乎有点偏离主题,但它是一个基本必须了解为什么 static 可以或应该与 constexpr 一起使用。不幸的是,这个基线有很多矛盾的信息浮动。

My previous paragraph seems a bit off-topic but it's a baseline necessary to understand why static can or should be used with constexpr. That baseline, unfortunately, has a lot of contradicting information floating around.

任何人都可以帮助我把所有这些信息一起变成纯事实与有意义的例子和概念?基本上与理解 constexpr 真的行为,为什么会使用 static static constexpr 有意义吗?

Can anyone help me pull all of this information together into pure facts with examples and concepts that make sense? Basically along with understanding how constexpr really behaves, why would you use static with it? And through what scopes/scenarios does static constexpr make sense, if they can be used together?

推荐答案

constexpr变量不是编译时值



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

constexpr variables are not compile-time values

A value is immutable and does not occupy storage (it has no address), however objects declared as constexpr can be mutable and do occupy storage (under the as-if rule).

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

Most objects declared as constexpr are immutable, but it is possible to define a constexpr object that is (partially) mutable as follows:

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
}



h3>

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

Storage

The compiler can, under the as-if rule, choose to not allocate any storage to store the value of an object declared as constexpr. Similarly, it can do such optimizations for non-constexpr variables. However, consider the case where we need to pass the address of the object to a function that is not inlined; for example:

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

这里的编译器需要为 / code>,
,以便将其地址传递给一些非内联函数。
编译器可以为预计算 i 连续分配存储;
可以想象这会影响效果的情况(见下文)。

The compiler here needs to allocate storage for precomputed, in order to pass its address to some non-inlined function. It is possible for the compiler to allocate the storage for precomputed and i contiguously; one could imagine situations where this might affect performance (see below).

变量是对象或引用 [basic] / 6

一个声明如 constexpr int a = 42; em> simple-declaration ;
它由 decl-specifier-seq init-declarator-list ;

A declaration like constexpr int a = 42; is gramatically a simple-declaration; it consists of decl-specifier-seq init-declarator-list ;

从[dcl.dcl] / 9,我们可以得出结论(但不严格),这样的声明声明一个对象。
具体来说,我们可以(严格地)断定它是一个对象声明
,但这包括引用声明。
另请参见有关是否可以包含类型 void 的变量的讨论。

From [dcl.dcl]/9, we can conclude (but not rigorously) that such a declaration declares an object. Specifically, we can (rigorously) conclude that it is an object declaration, but this includes declarations of references. See also the discussion of whether or not we can have variables of type void.

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

The constexpr in the declaration of an object implies that the object's type is const [dcl.constexpr]/9. An object is a region of storage[intro.object]/1. We can infer from [intro.object]/6 and [intro.memory]/1 that every object has an address. Note that we might not be able to directly take this address, e.g. if the object is referred to via a prvalue. (There are even prvalues which are not objects, such as the literal 42.) Two distinct complete objects must have different addresses[intro.object]/6.

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

From this point, we can conclude that an object declared as constexpr must have a unique address with respect to any other (complete) object.

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

Furthermore, we can conclude that the declaration constexpr int a = 42; declares an object with a unique address.

IMHO唯一有趣的问题是每个函数 static ,àla

The IMHO only interesting issue is the "per-function static", à la

void foo() {
    static constexpr int i = 42;
}

据我所知 - 这似乎仍然不完全清除 - 编译器可以计算 constexpr 变量在运行时。
但这似乎病态;让我们假设它不是这样做,
,即它在编译时预先计算初始化器。

As far as I know -- but this seems still not entirely clear -- the compiler may compute the initializer of a constexpr variable at run-time. But this seems pathological; let's assume it does not do that, i.e. it precomputes the initializer at compile-time.

初始化 static constexpr 局部变量在静态初始化期间完成,
必须在任何动态初始化之前执行 [basic.start.init] / 2 。
虽然不能保证,我们可以假设这不会强加运行时/加载时成本。
另外,由于常数初始化没有并发问题,
我认为我们可以放心地假设这不需要一个线程安全的运行时检查 code> static
变量已经初始化。
(查看clang和gcc的源代码应该能解释这些问题。)

The initialization of a static constexpr local variable is done during static initializtion, which must be performed before any dynamic initialization[basic.start.init]/2. Although it is not guaranteed, we can probably assume that this does not impose a run-time/load-time cost. Also, since there are no concurrency problems for constant initialization, I think we can safely assume this does not require a thread-safe run-time check whether or not the static variable has already been initialized. (Looking into the sources of clang and gcc should shed some light on these issues.)

对于非静态局部变量的初始化,
有时编译器不能在常量初始化期间初始化变量:

For the initialization of non-static local variables, there are cases where the compiler cannot initialize the variable during constant initialization:

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(&i);
    if(i > 0) recurse(i-1);
}

int main() {
    int i;
    std::cin >> i;
    recurse(i);
}



结论



看起来,在某些情况下,我们可以从 static constexpr 变量的静态存储持续时间中受益。
然而,我们可能会丢失这个局部变量的局部性,如本答案的存储一节所示。
直到我看到一个基准,显示这是一个真正的效果,
我将假设这是不相关的。

Conclusion

As it seems, we can benefit from static storage duration of a static constexpr variable in some corner cases. However, we might lose the locality of this local variable, as shown in the section "Storage" of this answer. Until I see a benchmark that shows that this is a real effect, I will assume that this is not relevant.

如果只有这两个效果 static constexpr 对象,
我会使用 static 每个默认值:
我们通常不需要为我们的 constexpr 对象保证唯一的地址。

If there are only these two effects of static on constexpr objects, I would use static per default: We typically do not need the guarantee of unique addresses for our constexpr objects.

对于可变的 constexpr 对象(具有 mutable 成员的类类型),
在局部 static 和非静态 constexpr 对象之间存在明显不同的语义。
类似地,如果地址本身的值是相关的(例如,对于哈希映射查找)。

For mutable constexpr objects (class types with mutable members), there are obviously different semantics between local static and non-static constexpr objects. Similarly, if the value of the address itself is relevant (e.g. for a hash-map lookup).

这篇关于什么时候和为什么要使用static和constexpr?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆