constexpr函数内的静态表格

6

我有一段生成的代码,用于将整数映射到整数,其核心是一个简单的表格。在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可能会在堆栈上,所以我想应该用constexpr替换static

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函数中禁止使用静态存储变量?这是什么动机引起的?

  • 有没有更简单的替代方案?当然,我可以将从中提取出来,但我想避免这样做。

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


1
请一次只提一个问题。 - Passer By
请注意,在constexpr函数中没有限制更改,因此通过在static_assert中添加注释,您的宏可以从C++14开始。 - Jarod42
2个回答

3

编辑:我的前一个回答是完全错误的...所以我要更正一下!

我知道我来晚了,我相信你已经解决了我要说的一切。

不幸的是,在constexpr函数内部不能有静态变量。

C++14标准规定,constexpr函数体中不能包含“具有...静态或线程存储期的变量定义”。这是有道理的,因为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?了解更多信息!

如果长度是“static const”,那么您应该符合所有标准,这是一个合理的方法。虽然使用std::size(或一些预C++17的替代实现,这很简单)会更容易。 - Asteroids With Wings
确实,在C++17中有更好的构造来确定数组长度。但我个人还没有遇到过使用它们获得任何好处的情况。毕竟,只是一个整数!(虽然我倾向于使用某种可变工厂来构建我的数组,这样所有操作都可以自动完成) - Tom Hickson
这是一个整数,你可能会忘记更改它。除非你写得很健壮,否则你编写的每一行代码都是潜在的失败点。 - Asteroids With Wings

1
  • 有没有更简洁的替代方案?我可以从转换中提取表格,但我想避免这样做。

那么怎么样使用以下代码(兼容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(或完美哈希)似乎是有效的实现。

所以我认为没有保证。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接