Mik*_*ail 9 c++ static constexpr
staticconstexpr函数内部不允许使用变量。这是有道理的,因为static会将状态引入应该是纯函数。
但是,我不明白为什么我们不能static constexpr在constexpr函数中使用变量。它保证始终具有相同的值,因此函数将保持纯净。
我为什么会在意?因为static在运行时会有所不同。考虑这个代码:
#include <array>
constexpr int at(const std::array<int, 100>& v, int index)
{
return v[index];
}
int foo1(int i) {
static constexpr std::array<int, 100> v = {
5, 7, 0, 0, 5 // The rest are zero
};
return at(v, i);
}
constexpr int foo2(int i) {
constexpr std::array<int, 100> v = {
5, 7, 0, 0, 5 // The rest are zero
};
return at(v, i);
}
int foo2_caller(int i) {
return foo2(i);
}
Run Code Online (Sandbox Code Playgroud)
直播:https : //gcc.godbolt.org/z/umdXgv
foo1有 3 个 asm 指令,因为它将缓冲区存储在静态存储中。虽然foo2有 15 条 asm 指令,因为每次调用都需要分配和初始化缓冲区,而编译器无法对其进行优化。
请注意,foo1这里只是为了显示foo2. 我想编写一个可以在编译和运行时使用的函数。这就是背后的想法foo2。但是我们看到它不能像 runtime-only 那样高效foo1,这令人不安。
我发现唯一有意义的相关讨论是 this,但它没有static constexpr具体讨论。
问题是:
static constexpr变量可能导致的一些问题?我的推理是否正确,或者我是否错过了静态 constexpr 变量可能导致的一些问题?
constexpr如果在constexpr上下文中允许静态存储持续时间,则在处理变量时,静态存储持续时间必须考虑一些边缘情况。
函数中具有静态存储持续时间的对象仅在第一次进入函数时构造。此时通常将存储支持应用于常量(对于运行时常量)。如果static constexpr在constexpr上下文中允许,则在编译时生成时必须发生以下两件事之一:
由于constexpr在整个上下文中本质上是无状态的,因此在constexpr函数调用期间应用静态存储对象会突然在constexpr调用之间添加状态——这对于当前的constexpr. 尽管constexpr函数可能会修改本地状态,但状态不会受到全局影响。
C++20 还放宽了constexpr要求,允许析构函数为 constexpr,这引发了更多问题,例如在上述情况下析构函数何时必须执行。
我并不是说这不是一个可以解决的问题;只是现有的语言工具在不违反某些规则的情况下使解决这个问题有点复杂。
使用自动存储持续时间对象,这更容易推理——因为存储是在某个时间点连贯地创建和销毁的。
有没有解决这个问题的建议?
没有我所知道的。已经在各种谷歌小组讨论过它的规则,但我没有看到任何关于此的建议。如果有人知道,请在评论中链接,我会更新我的答案。
有几种方法可以避免此限制,具体取决于您想要的 API 是什么以及要求是什么:
detail命名空间下。这使您的常量全局化,这可能会也可能不会满足您的要求。static常量。如果数据需要模板化,可以使用它,并允许您使用和发送控制对这个常量的访问。structclassprivatefriendstatic的struct/上的函数class(如果这符合您的要求)。如果数据需要作为模板,所有这三种方法都可以很好地工作,尽管方法 1 仅适用于 C++14(C++11 没有变量模板),而方法 2 和 3 可以在 C++ 中使用11.
在我看来,就封装而言,最干净的解决方案是将数据和执行函数都移动到structor 中的第三种方法class。这使数据与功能密切相关。例如:
class foo_util
{
public:
static constexpr int foo(int i); // calls at(v, i);
private:
static constexpr std::array<int, 100> v = { ... };
};
Run Code Online (Sandbox Code Playgroud)
这将生成与您的foo1方法相同的程序集,同时仍然允许它是constexpr.
如果将函数放入 a classorstruct不可能满足您的要求(也许这需要是一个免费函数?),那么您要么将数据移动到文件范围(可能受detail命名空间约定保护),要么通过把它扔到一个不相交的struct或 class处理数据的地方。后一种方法可以使用访问修饰符和友谊来控制数据访问。这个解决方案可以工作,但不可否认它并不那么干净:
#include <array>
constexpr int at(const std::array<int, 100>& v, int index)
{
return v[index];
}
constexpr int foo(int i);
namespace detail {
class foo_holder
{
private:
static constexpr std::array<int, 100> v = {
5, 7, 0, 0, 5 // The rest are zero
};
friend constexpr int ::foo(int i);
};
} // namespace detail
constexpr int foo(int i) {
return at(detail::foo_holder::v, i);
}
Run Code Online (Sandbox Code Playgroud)
这再次产生相同的程序集,foo1同时仍然允许它是constexpr.
这个怎么样?我将数组粘贴到非类型模板参数中:
template<std::array<int, 100> v = {5, 7, 0, 0, 5}>
constexpr int foo2(int i) {
return at(v, i);
}
Run Code Online (Sandbox Code Playgroud)
在godbolt上,foo2现在的反汇编与您的foo1. 目前这适用于 GCC,但不适用于 clang;看起来 clang 落后于 C++20 标准(参见这个SO 问题)。