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可以是可变的并且占用存储(在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,以便将其地址传递给某些非内联函数.这是可能的编译器分配的存储的precomputed和i连续; 可以想象这可能影响性能的情况(见下文).
变量是对象或引用[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;声明了一个具有唯一地址的对象.
恕我直言唯一有趣的问题是"每个功能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在某些极端情况下,我们可以从变量的静态存储持续时间中受益.但是,我们可能会丢失此局部变量的局部性,如本答案的"存储"部分所示.直到我看到一个基准测试表明这是一个真实的效果,我将假设这是不相关的.
如果只有这两种效应static的constexpr对象,我会用static每默认:我们通常不需要唯一地址为我们的保障constexpr对象.
对于可变constexpr对象(具有mutable成员的类类型),本地static和非静态constexpr对象之间显然存在不同的语义.类似地,如果地址本身的值是相关的(例如,对于散列图查找).
仅举例.社区维基.
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在这种情况下,我认为目前没有任何理由可以使用.