我有这段生成的代码,它们将int映射到int,其核心是一个简单的表。在C ++ 17之前的版本中,它通常是这样的:
int convert (int v)
{
static const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 };
if (0 <= v && v < sizeof table / sizeof table[0])
return table[v];
else
return -1;
}
对于C ++ 17,我想使用constexpr。我希望将constexpr
添加到函数签名中就足够了,但是我必须删除表的static
,这显然使我的实现更加复杂,没有充分的理由。不太提及在非constexpr上下文中的table
可能会在堆栈上,所以我想我应该用static
替换 constexpr
。
G ++ 8报告:
/tmp/foo.cc: In function 'constexpr int convert(int)':
/tmp/foo.cc:14:26: error: 'table' declared 'static' in 'constexpr' function
static const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 };
^
和Clang ++ 7:
/tmp/foo.cc:14:20: error: static variable not permitted in a constexpr function
static const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 };
^
1 error generated.
由于我希望这段代码可以与所有C ++标准一起使用(并在每种情况下都做正确的事情),因此我认为我必须编写此代码(是的,宏,这不是问题):
#if 201703L <= __cplusplus
# define CONSTEXPR constexpr
# define STATIC_ASSERT static_assert
# define STATIC_OR_CONSTEXPR constexpr
#else
# include <cassert>
# define CONSTEXPR
# define STATIC_ASSERT assert
# define STATIC_OR_CONSTEXPR static
#endif
CONSTEXPR int convert (int v)
{
STATIC_OR_CONSTEXPR const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 };
if (0 <= v && v < sizeof table / sizeof table[0])
return table[v];
else
return -1;
}
int main()
{
STATIC_ASSERT(convert(-42) == -1);
STATIC_ASSERT(convert(2) == 6);
STATIC_ASSERT(convert(7) == 8);
STATIC_ASSERT(convert(8) == -1);
}
所以:
是什么促使拦截在constexpr函数中具有静态存储变量?
是否有我可能错过的更清洁的选择?当然,我可以从table
中抽出convert
,但我想避免这种情况。
是否标准地保证非constexpr上下文中constexpr函数中的const数组将位于静态存储中而不是堆栈中?
答案 0 :(得分:2)
编辑:我先前的回答完全是错误的……所以我要解决这个问题!
我知道我对此事已经很晚了,我相信你已经解决了我必须说的一切。
幸运的是,您不能在constexpr函数中包含静态变量。
C ++ 14标准规定constexpr函数体不能包含“ ... static 变量的定义或线程存储持续时间”。这是有道理的,因为在这些运行时构造之外评估了constexpr上下文。
但是我们使用的是C ++而不是C,这意味着我们有对象!对象可以具有constexpr静态存储!我的解决方案是将函数(作为静态函数)包装在包含数据作为constexpr静态成员的对象/类型中。
我了解静态变量会在非constexpr世界中引发初始化问题,因此您可能需要使用预处理器根据要编译的标准来选择工作版本。
有了这个,C ++ 14代码变为:
class convert {
static constexpr int table[] = {3, 2, 6, 1, 7, 1, 6, 8};
public:
static constexpr int run(int v) {
if (0 <= v && v < sizeof table / sizeof table[0])
return table[v];
else
return -1;
}
};
您可能还想添加一个static constexpr int/unsigned int/size_t
来指定数组的长度,以删除您必须做的难看的sizeof
事情(尽管如果要针对多个标准,则可能不是可行)。
您可以改为将此指定为模板参数(我认为)应该适用于所有标准!
最后一个问题。我很确定constexpr函数中的任何非静态constexpr对象都将在非constexpr上下文中在堆栈上初始化(在Compiler Explorer上进行一点测试似乎与此相符)。主要区别在于,将仅以“无”运行时成本(constexpr存在的原因)直接构造结果值。
与其他答案相比,我觉得这更接近于您最初的想法,并且有时模板会让您头疼(尽管我个人会使用可变参数模板方法生成数组)
有关更多信息,请参见https://en.cppreference.com/w/cpp/language/constexpr和Does static constexpr variable inside a function make sense?!
答案 1 :(得分:0)
- 是否有我可能错过的更清洁的选择?当然,我可以将表从convert中拉出,但是我想避免这种情况。
(与C ++ 11兼容)怎么样:
template <int... Ns>
struct MyTable
{
static constexpr int get(std::size_t v)
{
return v < sizeof...(Ns) ? data[v] : -1;
}
static constexpr int data[] = {Ns...};
};
constexpr int convert (std::size_t v)
{
using table = MyTable<3, 2, 6, 1, 7, 1, 6, 8>;
return table::get(v);
}
- 标准是否保证非constexpr上下文中constexpr函数中的const数组将位于静态存储中而不是堆栈中?
我认为constexpr变量不需要实现,将代码转换为switch(或完善的哈希)似乎是有效的实现。
所以我认为没有保证。