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

时间:2019-07-25 22:40:32

标签: c++ arrays multidimensional-array initialization constexpr

我正在尝试初始化多维数组,尽管有可能在启动时填充一次该数组,但我确实希望该数组为constexpr,所以我想知道是否有一种方法可以获取编译器会为我执行此操作,特别是因为我可以提供一个constexpr函数,该函数接受每个索引的参数并返回数组应位于索引处的值。

例如:

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

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

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

我是否可以做些什么来使编译器填充此类数组,还是我不得不将test_values设置为实际上非constexpr并在运行时初始化一次? >

编辑:

为澄清起见,我本来就不反对使用std :: array而不是内置的C样式数组,但是我不认为std :: arrays对多个维度特别友好,并且使用一维数组混淆了我的程序需要做的事情(坦率地说,我愿意将其实现为一维std :: array,但是多维数组比具有相同大小的一维数组更容易混淆被手动解开,这就是为什么我用多维C数组描述它的原因。

2 个答案:

答案 0 :(得分:0)

使用元编程

C ++不允许从函数返回文字数组(请参见https://stackoverflow.com/a/4264495),但是正如其他人所述,返回std::array<>会导致功能上相同的内存内容。

AFAICT以下方法在gcc,msvc和clang中生成预烘焙的常量(.rodata节)。我归纳为3维。不幸的是,它还会使任何大小合适的数组(如64x64x64)上的编译器崩溃,并出现编译器错误virtual memory exhausted: Cannot allocate memory。所以我认为这不太实用。 [FWIW,32x32x32确实成功了]

基本方法是为每个包含数组索引0,1,2,...,NumDim-1的维创建一个parameter pack,并为较大的维创建固定的索引。然后以与std::experimental::make_array类似的方式返回一个std::array,其内容是应用于Value(x, y, z)函数的索引。

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

答案 1 :(得分:0)

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数组,则可以将其包装在结构中并使用类似的代码。