static
变量不允许在constexpr
函数中使用,这是有意义的,因为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);
}
实时:https://gcc.godbolt.org/z/umdXgvfoo1
有3条asm指令,因为它将缓冲区存储在静态存储器中。而foo2
有15条asm指令,因为它需要在每次调用时分配和初始化缓冲区,而编译器无法对此进行优化。
注意foo1
在这里只是为了显示foo2
中的缺陷。我想写一个在编译和运行时都能使用的函数。这就是foo2
背后的想法。但是我们看到它不能像只在运行时使用的foo1
那样有效,这是令人不安的。
我找到的唯一有意义的相关讨论是is this,但它没有具体讨论static constexpr
。
这些问题是:
- 我的推理正确吗?还是我忽略了
static constexpr
变量可能导致的一些问题? - 是否有任何解决这一问题的建议?
2条答案
按热度按时间j5fpnvbx1#
我的推理正确吗?还是我忽略了静态constexpr变量可能导致的一些问题?
在处理
constexpr
变量时,如果允许它们在constexpr
上下文中具有静态存储持续时间,则静态存储持续时间必须考虑一些边缘情况。函数中具有静态存储持续时间的对象仅在 * 第一次进入函数 * 时构造。通常在此时将存储备份应用于常量(对于运行时常量)。如果
constexpr
上下文中允许static constexpr
,则在编译时生成时必须发生以下两种情况之一:由于
constexpr
在整个上下文中本质上是无状态的,因此在constexpr
函数调用期间应用静态存储对象会突然在constexpr
调用之间添加状态--这对于constexpr
的当前规则来说是一个很大的变化。C++20也放宽了
constexpr
的要求,允许析构函数为constexpr,这就带来了更多的问题,比如在上述情况下,析构函数何时必须执行。我并不是说这不是一个"可以解决的问题";只是现有的语言设施使得在不违反某些规则的情况下解决这个问题有点复杂。
对于自动存储持续时间对象,这更容易理解--因为存储是在某个时间点一致地创建和销毁的。
是否有任何解决这一问题的建议?
据我所知,没有。在谷歌的各个小组里都有关于它的规则的讨论,但是我还没有看到任何关于它的建议。如果有人知道任何建议,请在评论中链接,我会更新我的回答。
变通方案
根据您所需的API和要求,有几种方法可以避免此限制:
1.把常量放到文件作用域中,可能是在
detail
命名空间下,这使得常量成为全局常量,这可能对你的要求有效,也可能不起作用。1.将常量抛出到
struct
/class
中的static
常量中。如果数据需要模板化,则可以使用此方法,并允许您使用private
和friend
来控制对此常量的访问。1.在包含数据的
struct
/class
上使函数成为static
函数(如果这符合您的要求)。如果数据需要作为模板,这三种方法都可以很好地工作,尽管方法1只适用于C14(C11没有变量模板),而方法2和3可以在C++11中使用。
在我看来,封装方面最干净的解决方案是第三种方法,即把数据和代理函数都移到
struct
或class
中,这样可以使数据与功能紧密关联,例如:Compiler Explorer Link
这将生成与
foo1
方法相同的程序集,同时仍然允许它是constexpr
。如果将函数放入
class
或struct
无法满足您的要求(也许这需要是一个自由函数?),那么您要么将数据移动到文件范围,要么将其删除(可能受detail
命名空间约定保护),或者把它扔到处理数据的不相交的struct
或class
中,后一种方法可以使用访问修饰符和友谊来控制数据访问,这种解决方案可以工作,尽管它确实不那么干净Compiler Explorer Link.
这再次产生与
foo1
相同的汇编,同时仍然允许它是constexpr
。iqih9akk2#
我把数组插入到一个非类型的模板参数中:
在godbolt上,
foo2
的反汇编现在与您的foo1
的反汇编相匹配。这目前可以在GCC上工作,但不能在clang上工作;看起来clang是C++20标准背后的原因(参见this SO问题)。