gpt4 book ai didi

c++ - 有没有办法在编译时从 initializer_list 构造?

转载 作者:行者123 更新时间:2023-12-05 05:53:09 27 4
gpt4 key购买 nike

我正在编写 C++ ndarray 类。我需要动态大小和编译时大小已知的数组(分别分配自由存储和分配堆栈)。我想支持从嵌套的 std::initializer_list 进行初始化。

动态大小的没问题:这很完美。

    frozenca::Array<float, 2> arr {{1, 2, 3}, {4, 5, 6}};
std::cout << arr.size() << '\n'; // 6
std::cout << arr[{1, 1}] << '\n'; // 5

静态大小的也可以:这很好用。

    frozenca::StaticArray<float, 2, 3> arr2 {{1, 2, 3}, {4, 5, 6}};
std::cout << arr2.size() << '\n'; // 6
std::cout << arr2[{1, 1}] << '\n'; // 5
// static_assert(arr2[{1, 1}] == 5); // THIS DOES NOT WORK

但我讨厌初始化实际上是在运行时完成的。我想把它作为编译时操作。我的实现细节如下:

template <std::semiregular T, std::size_t N>
using DenseInitializer_t = typename DenseInitializer<T, N>::type;

template <std::semiregular T, std::size_t N>
struct DenseInitializer {
using type = std::initializer_list<DenseInitializer_t<T, N - 1>>;
};

template <std::semiregular T>
struct DenseInitializer<T, 1> {
using type = std::initializer_list<T>;
};

template <std::semiregular T>
struct DenseInitializer<T, 0>;

template <std::semiregular Scalar, int... Sizes>
template <typename Initializer>
constexpr void StaticArray<Scalar, Sizes...>::verifyDims(const Initializer& init) const {
checkDims<0, Initializer, Sizes...>(init);
}

template <std::semiregular Scalar, int... Sizes>
StaticArray<Scalar, Sizes...>::StaticArray(DenseInitializer_t<value_type, N> init) {
verifyDims(init);
insertFlat(data(), init);
}

template <typename Initializer>
constexpr bool checkNonJagged(const Initializer& init) {
auto i = std::cbegin(init);
return std::all_of(init.begin(), init.end(), [&i](const auto& it) {
return it.size() == i->size();
});
}

template <int k, typename Initializer, int... Sizes>
void checkDims(const Initializer& init) {
if constexpr (k < sizeof...(Sizes) - 1) {
if (!checkNonJagged(init)) {
throw std::invalid_argument("Jagged matrix initializer");
}
}
if (std::get<k>(std::forward_as_tuple(Sizes...)) != std::ssize(init)) {
throw std::invalid_argument("Matrix initializer does not match with static matrix");
}
if constexpr (k < sizeof...(Sizes) - 1) {
checkDims<k + 1, decltype(*std::begin(init)), Sizes...>(*std::begin(init));
}
}

template <std::semiregular T>
void addList(T* data,
const T* first, const T* last,
int& index) {
for (; first != last; ++first) {
data[index] = *first;
++index;
}
}

template <std::semiregular T, typename I>
void addList(T* data,
const std::initializer_list<I>* first, const std::initializer_list<I>* last,
int& index) {
for (; first != last; ++first) {
addList(data, first->begin(), first->end(), index);
}
}

template <std::semiregular T, typename I>
void insertFlat(T* data, std::initializer_list<I> list) {
int index = 0;
addList(data, std::begin(list), std::end(list), index);
}

addListcheckNonJagged 不能作为编译时工作,因为编译器认为 init 即使在编译时也不知道示例案例。

如何将其作为编译时操作?

最佳答案

是的,constexpr std::initializer_list 没有理由在编译时初始化中不可用。

从您的代码片段中,不清楚您是否对 StaticArray 成员使用了类内初始化,因此您可能遇到的问题之一是 constexpr 构造函数不能对成员使用普通构造函数会将它们初始化为未指定的运行时值。

因此,您的示例的解决方法是默认初始化 StaticArray 成员并为构造函数、checkDims、addList 和数据指定 constexpr。要使用在编译时验证的 constexpr std::initializer_list 初始化运行时 StaticArray,您可以使用立即函数使初始化表达式明确地进行常量计算。

正如您可能意识到的那样,不可能在编译时初始化运行时变量,所以这是最好的办法。

如果您想要在编译时验证依赖于运行时变量的 std::initializer_list 的维度,则无法完成——std::initializer_list 不是 constexpr,因此它的大小不是'要么。相反,您可以围绕 Scalar 定义一个包装器类型,将其默认构造函数标记为已删除,并在 StaticArray 构造函数中接受这些包装器的聚合类型,例如所需维度的嵌套 std::array,或者避免双括号,一个 C 风格的多维数组。然后,如果维度不匹配,编译将因以下两个原因之一而失败:太多的初始化程序或使用已删除的默认构造函数。

下面的代码使用支持 C++20 的每个 GCC、Clang、MSVC 版本在 godbolt 上编译。

#include <algorithm>
#include <array>
#include <concepts>
#include <iostream>
#include <numeric>
#include <tuple>
#include <utility>

namespace frozenca {

template <std::size_t sz0, std::size_t... sz>
constexpr std::size_t prod() {
if constexpr (sizeof...(sz) == 0) {
return sz0;
} else {
return sz0 * prod<sz...>();
}
}

template <std::semiregular T, std::size_t N>
struct DenseInitializer;

template <std::semiregular T, std::size_t N>
using DenseInitializer_t = typename DenseInitializer<T, N>::type;

template <std::semiregular T, typename I>
constexpr void insertFlat(T* data, std::initializer_list<I> list);

template <int k, typename Initializer, int... Sizes>
constexpr void checkDims(const Initializer& init);

template <std::semiregular Scalar, int... Sizes>
struct StaticArray {
using value_type = Scalar;
static constexpr std::size_t N = sizeof...(Sizes);

Scalar body[prod<Sizes...>()];

constexpr Scalar* data() {
return body;
}

constexpr std::size_t size() const {
return std::size(body);
}

// no bound checks performed
constexpr Scalar operator[](const std::array<std::size_t, N>& index) const {
std::size_t dim = 0, idx = 0;
((idx = idx * Sizes + index[dim++]), ...);
return body[idx];
}

void print() const {
for (const auto& i: body) {
std::cout << i << ' ';
}
std::cout << std::endl;
}

template <typename Initializer>
constexpr void verifyDims(const Initializer& init) const;

constexpr StaticArray(DenseInitializer_t<value_type, N> init);
};


template <std::semiregular T, std::size_t N>
struct DenseInitializer {
using type = std::initializer_list<DenseInitializer_t<T, N - 1>>;
};

template <std::semiregular T>
struct DenseInitializer<T, 1> {
using type = std::initializer_list<T>;
};

template <std::semiregular T>
struct DenseInitializer<T, 0>;

template <std::semiregular Scalar, int... Sizes>
template <typename Initializer>
constexpr void StaticArray<Scalar, Sizes...>::verifyDims(const Initializer& init) const {
checkDims<0, Initializer, Sizes...>(init);
}

template <std::semiregular Scalar, int... Sizes>
constexpr StaticArray<Scalar, Sizes...>::StaticArray(DenseInitializer_t<value_type, N> init) : body{} {
verifyDims(init);
insertFlat(data(), init);
}

template <typename Initializer>
constexpr bool checkNonJagged(const Initializer& init) {
auto i = std::cbegin(init);
return std::all_of(init.begin(), init.end(), [&i](const auto& it) {
return it.size() == i->size();
});
}

template <int k, typename Initializer, int... Sizes>
constexpr void checkDims(const Initializer& init) {
if constexpr (k < sizeof...(Sizes) - 1) {
if (!checkNonJagged(init)) {
throw std::invalid_argument("Jagged matrix initializer");
}
}
if (std::get<k>(std::forward_as_tuple(Sizes...)) != std::ssize(init)) {
throw std::invalid_argument("Matrix initializer does not match with static matrix");
}
if constexpr (k < sizeof...(Sizes) - 1) {
checkDims<k + 1, decltype(*std::begin(init)), Sizes...>(*std::begin(init));
}
}

template <std::semiregular T>
constexpr void addList(T* data,
const T* first, const T* last,
int& index) {
for (; first != last; ++first) {
data[index] = *first;
++index;
}
}

template <std::semiregular T, typename I>
constexpr void addList(T* data,
const std::initializer_list<I>* first, const std::initializer_list<I>* last,
int& index) {
for (; first != last; ++first) {
addList(data, first->begin(), first->end(), index);
}
}

template <std::semiregular T, typename I>
constexpr void insertFlat(T* data, std::initializer_list<I> list) {
int index = 0;
addList(data, std::begin(list), std::end(list), index);
}

}

consteval auto echo(std::copy_constructible auto val) {
return val;
}

void check0() {
// frozenca::StaticArray<float, 2, 3> arr_jagged {{1, 2, 3}, {4, 5}}; // throws an exception
frozenca::StaticArray<float, 2, 3> arr2 {{1, 2, 3}, {4, 5, 6}};
std::cout << arr2.size() << '\n'; // 6
std::cout << arr2[{1, 1}] << '\n'; // 5
// static_assert(arr2[{1, 1}] == 5); // THIS DOES NOT WORK
arr2.print();
}

void check1() {
// constexpr frozenca::StaticArray<float, 2, 3> arr_jagged {{1, 2, 3}, {4, 5}}; // compile-time error
constexpr frozenca::StaticArray<float, 2, 3> arr2 {{1, 2, 3}, {4, 5, 6}};
static_assert(arr2.size() == 6);
static_assert(arr2[{1, 1}] == 5);
arr2.print();
}

void check2() {
// auto arr_jagged = echo(frozenca::StaticArray<float, 2, 3>{{1, 2, 3}, {4, 5}}); // compile-time error
auto arr2 = echo(frozenca::StaticArray<float, 2, 3>{{3, 2, 1}, {6, 5, 4}});
std::cout << arr2.size() << '\n'; // 6
std::cout << arr2[{1, 1}] << '\n'; // 5
// static_assert(arr2[{1, 1}] == 5); // THIS DOES NOT WORK
arr2.print();
}

namespace aggregate {

struct NoDefault {
int val;

constexpr NoDefault() = delete;
constexpr NoDefault(int val) : val{val} {};
constexpr operator int() const { return val; }
};

template <std::size_t... sizes>
struct NDNested;

template <std::size_t... sizes>
using NDNested_t = typename NDNested<sizes...>::type;

template <>
struct NDNested<> {
using type = NoDefault;
};

template <std::size_t size0, std::size_t... sizes>
struct NDNested<size0, sizes...> {
using type = NDNested_t<sizes...>[size0];
};

template <std::size_t sz0, std::size_t... sizes>
constexpr int sum(const NDNested_t<sz0, sizes...>& t) {
if constexpr (sizeof...(sizes) != 0) {
constexpr auto op = [](int acc, const auto& arr) { return acc + sum<sizes...>(arr); };
return std::accumulate(std::begin(t), std::end(t), 0, op);
} else {
return std::accumulate(std::begin(t), std::end(t), 0);
}
}

}

void check_aggregate() {
using aggregate::sum;
#ifndef _MSC_VER
static_assert(sum<2, 2, 2>({{{1, 1}, {2, 4}}, {{1, 2}, {3, 4}}}) == 18);
#endif
int x = 100;
std::cout << sum<2, 2, 2>({{{1, 1}, {2, 4}}, {{1, 2}, {x, 4}}}) << '\n';
// std::cout << sum<2, 2, 2>({{{1, 1}, {2, 4}}, {{1, 2}, {x}}}) << '\n'; // deleted constructor
// std::cout << sum<2, 2, 2>({{{1, 1}, {2, 4}}, {{1, 2}, {x, 3, 4}}}) << '\n'; // excess elements
}

int main() {
check0();
check1();
check2();
check_aggregate();
}

关于c++ - 有没有办法在编译时从 initializer_list 构造?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69962467/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com