有没有办法在编译时从 initializer_list 构造?
Is there way to construct from initializer_list in compile time?
我正在编写 C++ ndarray class。我需要动态大小和编译时大小已知的数组(分别分配自由存储和分配堆栈)。
我想支持从嵌套 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);
}
addList
和 checkNonJagged
不能用作编译时,因为编译器认为 init
即使在示例案例中也不被称为编译时。
如何将其作为编译时操作?
是的,constexpr std::initializer_list 没有理由在 compile-time 初始化中不可用。
从您的代码片段中,不清楚您是否对 StaticArray 成员使用了 in-class 初始化,因此您可能 运行 遇到的问题之一是 constexpr 构造函数不能' t 为成员使用普通构造函数,将它们初始化为未指定的 run-time 值。
因此,您的示例的解决方法是 default-initialize StaticArray 成员并为构造函数、checkDims、addList 和数据指定 constexpr。要使用在 compile-time 处验证的 constexpr std::initializer_list 初始化 运行time StaticArray,您可以使用立即函数 constant-evaluated 使初始化表达式显式 constant-evaluated。
正如您可能意识到的那样,不可能在 compile-time 处初始化一个 run-time 变量,所以这是最好的办法。
如果您想要在 compile-time 处验证依赖于 运行 时间变量的 std::initializer_list 的维度,则无法完成 -- std::initializer_list 不是 constexpr,所以它的大小也不是。相反,您可以围绕 Scalar 定义一个包装器类型,将其默认构造函数标记为已删除,并在 StaticArray 构造函数中接受这些包装器的聚合类型,例如所需维度的嵌套 std::array,或者,为了避免双重大括号,一个 C-style 多维数组。然后,如果尺寸不匹配,编译将因以下两个原因之一而失败:太多的初始化程序或使用已删除的默认构造函数。
下面的代码在 godbolt 上使用支持 C++20 的每个 GCC、Clang、MSVC 版本编译。
#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++ ndarray class。我需要动态大小和编译时大小已知的数组(分别分配自由存储和分配堆栈)。
我想支持从嵌套 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);
}
addList
和 checkNonJagged
不能用作编译时,因为编译器认为 init
即使在示例案例中也不被称为编译时。
如何将其作为编译时操作?
是的,constexpr std::initializer_list 没有理由在 compile-time 初始化中不可用。
从您的代码片段中,不清楚您是否对 StaticArray 成员使用了 in-class 初始化,因此您可能 运行 遇到的问题之一是 constexpr 构造函数不能' t 为成员使用普通构造函数,将它们初始化为未指定的 run-time 值。
因此,您的示例的解决方法是 default-initialize StaticArray 成员并为构造函数、checkDims、addList 和数据指定 constexpr。要使用在 compile-time 处验证的 constexpr std::initializer_list 初始化 运行time StaticArray,您可以使用立即函数 constant-evaluated 使初始化表达式显式 constant-evaluated。
正如您可能意识到的那样,不可能在 compile-time 处初始化一个 run-time 变量,所以这是最好的办法。
如果您想要在 compile-time 处验证依赖于 运行 时间变量的 std::initializer_list 的维度,则无法完成 -- std::initializer_list 不是 constexpr,所以它的大小也不是。相反,您可以围绕 Scalar 定义一个包装器类型,将其默认构造函数标记为已删除,并在 StaticArray 构造函数中接受这些包装器的聚合类型,例如所需维度的嵌套 std::array,或者,为了避免双重大括号,一个 C-style 多维数组。然后,如果尺寸不匹配,编译将因以下两个原因之一而失败:太多的初始化程序或使用已删除的默认构造函数。
下面的代码在 godbolt 上使用支持 C++20 的每个 GCC、Clang、MSVC 版本编译。
#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();
}