constexpr函数中的静态表

时间:2018-12-27 07:20:29

标签: c++ c++17 constexpr

我有这段生成的代码,它们将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数组将位于静态存储中而不是堆栈中?

2 个答案:

答案 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/constexprDoes 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);
}

Demo

  
      
  • 标准是否保证非constexpr上下文中constexpr函数中的const数组将位于静态存储中而不是堆栈中?
  •   

我认为constexpr变量不需要实现,将代码转换为switch(或完善的哈希)似乎是有效的实现。

所以我认为没有保证。