如何在编译时初始化一个 constexpr 多维数组?

How to initialize a constexpr multidimensional array at compile time?

我正在尝试初始化一个多维数组,虽然可以在启动时填充一次这个数组,但我真的更希望数组是 constexpr,所以我想知道是否有办法让编译器为我做这件事,特别是因为我可以提供一个 constexpr 函数,它为每个索引和 returns 数组应该在索引处的值提供参数。

例如:

constexpr bool test_values[64][64][64][64] = {
... // magic goes here
};

我有一个函数 constexpr bool f(int,int,int,int) 可以告诉我每个元素应该是什么。我更愿意通过数组访问条目,因为进行数组查找比为非常量值调用 f() 更快。

我发现的大多数与在运行时初始化数组有关的其他问题都使用 std::array 而不是 C 数组,而且我能找到的 none 是多维的。我曾尝试将多维数组展开为一维数组并使用 std::array 方法,例如我在 this question 的一个答案中找到的方法,但我发现 gcc 9.1 生成的结果代码仍然在启动时填充数组一次,而不是编译器直接生成数组。

我能做些什么来让编译器填充这种数组,还是我不得不离开 test_values 作为有效的非 constexpr,并在运行时初始化一次?

编辑:

澄清一下,我本质上并不反对使用 std::array 而不是内置的 C 风格数组,但我不认为 std::array 对多维特别友好,并且使用一维数组混淆了我的程序需要做的事情(坦率地说,如果必须的话,我愿意将其实现为一维 std::array,但多维数组感觉不如等效的混淆手动展开的一维尺寸,这就是为什么我用多维 C 数组来描述它的原因)。

使用元编程

C++ 不允许从函数返回文字数组(参见 ),但正如其他人所说,返回 std::array<> 会导致功能相同的内存内容。

AFAICT 以下方法在 gcc、msvc 和 clang 中生成预烘焙常量(.rodata 部分)。我概括为 3 个维度。不幸的是,它还会在任何合适大小的数组(如 64x64x64)上炸毁编译器并出现编译器错误 virtual memory exhausted: Cannot allocate memory。所以我认为它不是很实用。 [FWIW,32x32x32 确实成功]

基本方法是创建一个 parameter pack for each dimension containing the array indices 0,1,2,...,NumDim-1, with fixed indices for the larger dimensions. Then return a std::array whose contents are the indices applied to the Value(x, y, z) function, in a similar fashion to std::experimental::make_array

https://godbolt.org/z/utDDBk

constexpr bool Value(size_t x, size_t y, size_t z)
{
    return (bool)((x ^ y ^ z) & 1);
}

namespace ValueArrayDetail {
    template <size_t NumX, size_t X>
    struct IteratorX
    {
        template <class... Xs>
        static constexpr std::array<bool, NumX> MakeXs(size_t z, size_t y, Xs... xs)
        {
            return IteratorX<NumX, X - 1>::template MakeXs(z, y, X - 1, xs...);
        }
    };
    template <size_t NumX>
    struct IteratorX<NumX, 0>
    {
        template <class... Xs>
        static constexpr std::array<bool, NumX> MakeXs(size_t z, size_t y, Xs... xs)
        {
            return { Value(xs, y, z)... };
        }
    };

    template <size_t NumX, size_t NumY, size_t Y>
    struct IteratorY
    {
        template <class... Ys>
        static constexpr std::array<std::array<bool, NumX>, NumY> MakeYs(size_t z, Ys... ys)
        {
            return IteratorY<NumX, NumY, Y - 1>::template MakeYs(z, Y - 1, ys...);
        }
    };
    template <size_t NumX, size_t NumY>
    struct IteratorY<NumX, NumY, 0>
    {
        template <class... Ys>
        static constexpr std::array<std::array<bool, NumX>, NumY> MakeYs(size_t z, Ys... ys)
        {
            return { IteratorX<NumX, NumX>::template MakeXs(z, ys)... };
        }
    };

    template <size_t NumX, size_t NumY, size_t NumZ, size_t Z>
    struct IteratorZ
    {
        template <class ... Zs >
        static constexpr std::array<std::array<std::array<bool, NumX>, NumY>, NumZ> MakeZs(Zs... zs)
        {
            return IteratorZ<NumX, NumY, NumZ, Z - 1>::template MakeZs(Z - 1, zs...);
        }
    };
    template <size_t NumX, size_t NumY, size_t NumZ>
    struct IteratorZ<NumX, NumY, NumZ, 0>
    {
        template <class... Zs>
        static constexpr std::array<std::array<std::array<bool, NumX>, NumY>, NumZ> MakeZs(Zs... zs)
        {
            return { IteratorY<NumX, NumY, NumY>::template MakeYs(zs)... };
        }
    };

    template <size_t NumX, size_t NumY, size_t NumZ>
    static constexpr std::array<std::array<std::array<bool, NumX>, NumY>, NumZ> MakeValues()
    {
        return IteratorZ<NumX, NumY, NumZ, NumZ>::template MakeZs();
    }
}

auto constexpr test_values = ValueArrayDetail::MakeValues<3, 4, 5>();

使用文字常量

您可以用文字常量初始化 test_values,方法与普通 const 数组相同。为每个维度使用嵌套括号。下面的例子有点懒惰,每行 64 个值只有 4 个值,但它在输出中清楚地显示了每个未明确指定的数据如何具有默认值零。

https://godbolt.org/z/cnzTn7

输入:

constexpr bool test_values[64][64][64][64] = {
    {
        {
            {true, false, false, true},
            {false, true, false, false},
            {true, true, true, true},
        },
        {
            {1, 0, 0, 1},
            {1, 1, 1, 0},
            {0, 0, 1, 1},
        },
    }
};

输出(x86-64 gcc 9.1):

test_values:
    .byte   1    <-- test_values[0][0][0][0]
    .byte   0
    .byte   0
    .byte   1
    .zero   60   <-- test_values[0][0][0][4 .. 63]
    .byte   0    <-- test_values[0][0][1][0]
    .byte   1
    .zero   62   <-- test_values[0][0][1][2 .. 63]
    .byte   1    <-- test_values[0][0][2][0]
    .byte   1
    .byte   1
    .byte   1
    .zero   60   <-- test_values[0][0][2][2 .. 63]
    .zero   3904
    .byte   1    <-- test_values[0][1][0][0]
    .byte   0
    .byte   0
    .byte   1
    .zero   60
    .byte   1
    .byte   1
    .byte   1
    .zero   61
    .byte   0
    .byte   0
    .byte   1
    .byte   1
    .zero   60
    .zero   3904
    .zero   253952
    .zero   16515072

C 数组不可复制,因此实际上不可能使用函数,但是使用 std::array,您可以创建 constexpr 函数(尽管 C++11 更受限制)

constexpr auto generate()
{
    std::array<std::array<std::array<std::array<bool, 64>, 64>, 64>, 64> res{};

    for (int a = 0; a != 64; ++a) {
        for (int b = 0; b != 64; ++b) {
            for (int c = 0; c != 64; ++c) {
                for (int d = 0; d != 64; ++d) {
                     res[a][b][c][d] = f(a, b, c, d);
                }
            }
        }
    }

    return res;
}

constexpr auto test_values = generate();

如果你真的需要 C 数组,你可以将它包装在一个结构中并使用类似的代码。