嵌套 initializer_list 用于初始化多维数组
Nested initializer_list for initializing multidimensional arrays
出于某些原因,我必须在 C++ 中实现多维数组 class。
有问题的数组是这样的:
template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
class Array final
{
private:
std::vector<size_t> shape_;
std::vector<T> data_;
public:
// Some public methods
}
T
是存储在数组中的元素类型,并且数组的维度不是模板化的,因为用户应该能够重塑数组,例如:
Array<int> array = Array<int>::zeros(3, 2, 4);
array.reshape(4, 6);
虽然上面提到的功能实现的很顺利,但是我卡在了实现这个class的"beginning",也就是初始化数组...
我的问题如下:
是否有任何方法可以拥有这样的构造函数,以便不同深度的嵌套初始化列表创建不同的数组,如:
Array<int> a = {1, 2}; // Dimension: 1, Shape: 2
Array<int> b = {{1}, {2}}; // Dimension: 2, Shape: 2x1
我实现构造函数的方法使这两个数组相同,这不是我想要的。另外,clang 抱怨支撑标量,这似乎是问题所在。目前我遇到上述问题的天真方法看起来像这样
...
Array() :data_(0), shape_{0} {}
Array(std::initializer_list<T> list) :data_(list), shape_{list.size()} {}
Array(std::initializer_list<Array> lists)
{
// Implementation
}
...
编译器很容易推导出以下数组的类型:
Array c = {1, 2}; // T = int
Array d = {1.0, 2.0}; // T = double
但是我没能写出多维的工作推导指南:
Array e = {{1, 2}, {3, 4}}; // Expects T = int
Array f = {{1.0, 2.0}, {3.0, 4.0}}; // Expects T = double
有什么方法可以为此class编写类型推导指南吗?
仅涉及 initializer_list
的唯一可能解决方案是声明多个构造函数,这些构造函数等于可能的维度数:
template<class T>
Array(std::initializer_list<T>)
template<class T>
Array(std::initializer_list<std::initializer_list<T>>)
...
原因在[temp.deduc.call]/1中给出:(P模板参数)
If removing references and cv-qualifiers from P gives std::initializer_list [...] and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument [...]
Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context
因此,如果函数参数是 std::initializer_list<T>
初始化列表参数的嵌套元素本身不能是初始化列表。
如果您不想声明那么多构造函数,另一种选择是显式指定参数类型为 std::initializer_list
以避免模板参数推导。下面我使用了一个名为 "nest" 的 class 只是因为它的名字更短:
#include<initializer_list>
using namespace std;
template<class T>
struct nest{
initializer_list<T> value;
nest(initializer_list<T> value):value(value){}
};
template<class T>
nest(initializer_list<T>)->nest<T>;
struct x{
template<class T>
x(initializer_list<T>);
};
int main(){
x a{1,2,3,4};
x b{nest{1,2},nest{3,4}};
x c{nest{nest{1},nest{2}},nest{nest{3},nest{4}}};
}
我可能有点晚了,但它 100% 可能没有多个构造函数下面是从 initilizer_list 中提取数据的源代码,它有点老套。整个技巧是使用正确的类型隐式调用构造函数。
#include <initializer_list>
#include <iostream>
using namespace std;
class ShapeElem{
public:
ShapeElem* next;
int len;
ShapeElem(int _len,ShapeElem* _next): next(_next),len(_len){}
void print_shape(){
if (next != nullptr){
cout <<" "<< len;
next->print_shape();
}else{
cout << " " << len << "\n";
}
}
int array_len(){
if (next != nullptr){
return len*next->array_len();
}else{
return len;
}
}
};
template<class value_type>
class ArrayInit{
public:
void* data = nullptr;
size_t len;
bool is_final;
ArrayInit(std::initializer_list<value_type> init) : data((void*)init.begin()), len(init.size()),is_final(true){}
ArrayInit(std::initializer_list<ArrayInit<value_type>> init): data((void*)init.begin()), len(init.size()),is_final(false){}
ShapeElem* shape(){
if(is_final){
ShapeElem* out = new ShapeElem(len,nullptr);
}else{
ArrayInit<value_type>* first = (ArrayInit<value_type>*)data;
ShapeElem* out = new ShapeElem(len,first->shape());
}
}
void assign(value_type** pointer){
if(is_final){
for(size_t k = 0; k < len;k ++ ){
(*pointer)[k] = ( ((value_type*)data)[k]);
}
(*pointer) = (*pointer) + len;
}else{
ArrayInit<value_type>* data_array = (ArrayInit<value_type>*)data;
for(int k = 0;k < len;k++){
data_array[k].assign(pointer);
}
}
}
};
int main(){
auto x = ArrayInit<int>({{1,2,3},{92,1,3}});
auto shape = x.shape();
shape->print_shape();
int* data = new int[shape->array_len()];
int* running_pointer = data;
x.assign(&running_pointer);
for(int i = 0;i < shape->array_len();i++){
cout << " " << data[i];
}
cout << "\n";
}
产出
2 3
1 2 3 92 1 3
shape() 函数将为您 return 您在每个维度上的张量形状。数组被准确地保存下来,因为它被写下来了。创建像形状这样的东西真的很重要,因为这会给你元素的顺序。
如果你想要张量中的特定索引,可以说 a[1][2][3]
正确的位置是 1*a.shape[1]a.shape[2] + 2a.shape[2] + 3
如果您不想创建张量或多维数组,我会建议将所有内容存储为列表,一维数组中条目的引用真的很复杂。这段代码应该仍然是一个很好的起点。
可以在以下位置找到一些小细节和技巧:https://github.com/martinpflaum/multidimensional_array_cpp
出于某些原因,我必须在 C++ 中实现多维数组 class。 有问题的数组是这样的:
template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
class Array final
{
private:
std::vector<size_t> shape_;
std::vector<T> data_;
public:
// Some public methods
}
T
是存储在数组中的元素类型,并且数组的维度不是模板化的,因为用户应该能够重塑数组,例如:
Array<int> array = Array<int>::zeros(3, 2, 4);
array.reshape(4, 6);
虽然上面提到的功能实现的很顺利,但是我卡在了实现这个class的"beginning",也就是初始化数组... 我的问题如下:
是否有任何方法可以拥有这样的构造函数,以便不同深度的嵌套初始化列表创建不同的数组,如:
Array<int> a = {1, 2}; // Dimension: 1, Shape: 2 Array<int> b = {{1}, {2}}; // Dimension: 2, Shape: 2x1
我实现构造函数的方法使这两个数组相同,这不是我想要的。另外,clang 抱怨支撑标量,这似乎是问题所在。目前我遇到上述问题的天真方法看起来像这样
... Array() :data_(0), shape_{0} {} Array(std::initializer_list<T> list) :data_(list), shape_{list.size()} {} Array(std::initializer_list<Array> lists) { // Implementation } ...
编译器很容易推导出以下数组的类型:
Array c = {1, 2}; // T = int Array d = {1.0, 2.0}; // T = double
但是我没能写出多维的工作推导指南:
Array e = {{1, 2}, {3, 4}}; // Expects T = int Array f = {{1.0, 2.0}, {3.0, 4.0}}; // Expects T = double
有什么方法可以为此class编写类型推导指南吗?
仅涉及 initializer_list
的唯一可能解决方案是声明多个构造函数,这些构造函数等于可能的维度数:
template<class T>
Array(std::initializer_list<T>)
template<class T>
Array(std::initializer_list<std::initializer_list<T>>)
...
原因在[temp.deduc.call]/1中给出:(P模板参数)
If removing references and cv-qualifiers from P gives std::initializer_list [...] and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument [...] Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context
因此,如果函数参数是 std::initializer_list<T>
初始化列表参数的嵌套元素本身不能是初始化列表。
如果您不想声明那么多构造函数,另一种选择是显式指定参数类型为 std::initializer_list
以避免模板参数推导。下面我使用了一个名为 "nest" 的 class 只是因为它的名字更短:
#include<initializer_list>
using namespace std;
template<class T>
struct nest{
initializer_list<T> value;
nest(initializer_list<T> value):value(value){}
};
template<class T>
nest(initializer_list<T>)->nest<T>;
struct x{
template<class T>
x(initializer_list<T>);
};
int main(){
x a{1,2,3,4};
x b{nest{1,2},nest{3,4}};
x c{nest{nest{1},nest{2}},nest{nest{3},nest{4}}};
}
我可能有点晚了,但它 100% 可能没有多个构造函数下面是从 initilizer_list 中提取数据的源代码,它有点老套。整个技巧是使用正确的类型隐式调用构造函数。
#include <initializer_list>
#include <iostream>
using namespace std;
class ShapeElem{
public:
ShapeElem* next;
int len;
ShapeElem(int _len,ShapeElem* _next): next(_next),len(_len){}
void print_shape(){
if (next != nullptr){
cout <<" "<< len;
next->print_shape();
}else{
cout << " " << len << "\n";
}
}
int array_len(){
if (next != nullptr){
return len*next->array_len();
}else{
return len;
}
}
};
template<class value_type>
class ArrayInit{
public:
void* data = nullptr;
size_t len;
bool is_final;
ArrayInit(std::initializer_list<value_type> init) : data((void*)init.begin()), len(init.size()),is_final(true){}
ArrayInit(std::initializer_list<ArrayInit<value_type>> init): data((void*)init.begin()), len(init.size()),is_final(false){}
ShapeElem* shape(){
if(is_final){
ShapeElem* out = new ShapeElem(len,nullptr);
}else{
ArrayInit<value_type>* first = (ArrayInit<value_type>*)data;
ShapeElem* out = new ShapeElem(len,first->shape());
}
}
void assign(value_type** pointer){
if(is_final){
for(size_t k = 0; k < len;k ++ ){
(*pointer)[k] = ( ((value_type*)data)[k]);
}
(*pointer) = (*pointer) + len;
}else{
ArrayInit<value_type>* data_array = (ArrayInit<value_type>*)data;
for(int k = 0;k < len;k++){
data_array[k].assign(pointer);
}
}
}
};
int main(){
auto x = ArrayInit<int>({{1,2,3},{92,1,3}});
auto shape = x.shape();
shape->print_shape();
int* data = new int[shape->array_len()];
int* running_pointer = data;
x.assign(&running_pointer);
for(int i = 0;i < shape->array_len();i++){
cout << " " << data[i];
}
cout << "\n";
}
产出
2 3
1 2 3 92 1 3
shape() 函数将为您 return 您在每个维度上的张量形状。数组被准确地保存下来,因为它被写下来了。创建像形状这样的东西真的很重要,因为这会给你元素的顺序。
如果你想要张量中的特定索引,可以说 a[1][2][3] 正确的位置是 1*a.shape[1]a.shape[2] + 2a.shape[2] + 3
如果您不想创建张量或多维数组,我会建议将所有内容存储为列表,一维数组中条目的引用真的很复杂。这段代码应该仍然是一个很好的起点。 可以在以下位置找到一些小细节和技巧:https://github.com/martinpflaum/multidimensional_array_cpp