Вы спрашивали это:
В: В любом случае, можно ли создать класс Array, который можно инициализировать с помощью фигурного списка инициализации, без необходимости вручную указывать длину массива и без функции 'make_array'.
Я работал над реализацией класса, который ведет себя так, как вы описываете, только он немного сложнее, но все еще довольно простой, читаемый, многоразовый, переносимый и универсальный.
Мне не удалось получить массив T items[]
в качестве прямого члена в классе. Вместо этого мне пришлось использовать T* items
и создать перегруженный operator[]
в производном классе, чтобы имитировать поведение массива. Это не означает, что здесь нет работы, как показали другие. Я просто нахожу, что это одно из возможных решений без указания размера массива.
Я использую базовый класс для хранения элементов из конструктора с помощью std::initializer_list
или variadic constructor
. Сами шаблоны классов не являются вариационными, только их конструктор. Базовый класс хранит значения из initializer_list
или parameter pack
в std::vector
. Унаследованный класс сохраняет содержимое из vector
в T*
, вызывая функцию data()
векторного класса.
template<typename T>
class ParamPack {
protected:
std::vector<T> values_;
size_t size_;
public:
template<typename... U>
ParamPack( U... u ) :
values_{ static_cast<T>(u)... },
size_( sizeof...(U) ) {}
template<typename ... U>
ParamPack( std::initializer_list<std::is_same<T, U...>( U...)> il ) :
values_( il ), size_( il.size() ) {}
std::vector<T>& operator()() { return values_; }
size_t size() const { return size_; }
};
template<typename T>
class Array : public ParamPack<T> {
private:
T* items_;
public:
template<typename... U>
Array( U... u ) : ParamPack<T>::ParamPack( u... ) {
items_ = this->values_.data();
}
template<typename... U>
Array( std::initializer_list<U...> il ) : ParamPack<T>::ParamPack( il ) {
items_ = this->values_.data();
}
T& operator[]( size_t idx ) {
return items_[idx];
}
T operator[]( size_t idx ) const {
return items_[idx];
}
T* data() const { return items_; }
};
int main() {
try {
// Parameter Pack Examples:
// Variadic Constructor { ... }
std::cout << "ParamPack<T> Examples:\n";
std::cout << "Using ParamPack<T>'s Variadic Constructor\n";
ParamPack<int> pp1( 1, 2, 3, 4 );
std::cout << "Size: " << pp1.size() << " | Elements: ";
for( auto& v : pp1() ) {
std::cout << v << " ";
}
std::cout << '\n';
std::cout << "Using ParamPack<T>'s Variadic Constructor with an Initializer List\n";
ParamPack<int> pp2( { 5, 6, 7, 8 } );
std::cout << "Size: " << pp2.size() << " | Elements: ";
for( auto& v : pp2() ) {
std::cout << v << " ";
}
std::cout << '\n';
std::cout << "Using ParamPack<T>'s initializer_list constructor\n";
std::initializer_list<int> il{ 9,10,11,12 };
ParamPack<int> pp3( il );
std::cout << "Size: " << pp3.size() << " | Elements: ";
for( auto& v : pp3() ) {
std::cout << v << " ";
}
std::cout << "\n\n";
// Array Examples:
std::cout << "Array<T> Examples:\n";
std::cout << "Using Array<T>'s initializer_list Constructor\n";
Array<int> arr( il );
for( size_t i = 0; i < arr.size(); i++ ) {
std::cout << arr[i] << " ";
}
std::cout << "\n";
// Using Variadic Constructor
std::cout << "Using Array<T>'s Variadic Constructor\n";
Array<int> testA( 9, 8, 7, 6 );
for( size_t i = 0; i < testA.size(); i++ ) {
std::cout << testA[i] << " ";
}
std::cout << '\n';
Array<std::string> testB( "Hello", " World" );
for( size_t i = 0; i < testB.size(); i++ ) {
std::cout << testB[i] << " ";
}
std::cout << "\n\n";
// Using Constructor w/ Initializer List
std::cout << "Using Array<T>'s Variadic Constructor with Initializer List\n";
Array<int> testC( { 105, 210, 420 } );
for( size_t i = 0; i < testC.size(); i++ ) {
std::cout << testC[i] << " ";
}
std::cout << "\n\n";
// Using Initializer List with =
std::cout << "Using Array<T>'s Initializer List with =\n";
Array<int> a = { 1, 2, 3, 4 };
for( size_t i = 0; i < a.size(); i++ ) {
std::cout << a[i] << " ";
}
std::cout << '\n';
Array<char> b = { 'a', 'b', 'c', 'd' };
for ( size_t i = 0; i < b.size(); i++ ) {
std::cout << b[i] << " ";
}
std::cout << '\n';
Array<double> c = { 1.2, 3.4, 4.5, 6.7 };
for( size_t i = 0; i < c.size(); i++ ) {
std::cout << c[i] << " ";
}
std::cout << "\n\n";
// Using Initializer List directly
std::cout << "Using Array<T>'s Initalizer List directly\n";
Array<uint32_t> a1{ 3, 6, 9, 12 };
for( size_t i = 0; i < a1.size(); i++ ) {
std::cout << a1[i] << " ";
}
std::cout << "\n\n";
// Using user defined data type
struct Point {
int x_, y_;
Point( int x, int y ) : x_( x ), y_( y ) {}
};
Point p1( 1, 2 ), p2( 3, 4 ), p3( 5, 6 );
// Variadic Constructor
std::cout << "Using Array<T>'s Variadic Consturctor with user data type\n";
Array<Point> d1( p1, p2, p3 );
for( size_t i = 0; i < d1.size(); i++ ) {
std::cout << "(" << d1[i].x_ << "," << d1[i].y_ << ") ";
}
std::cout << '\n';
// Initializer List Construtor (reversed order)
std::cout << "Using Array<T>'s Initializer List Constructor with user data type\n";
Array<Point> d2( { p3, p2, p1 } );
for( size_t i = 0; i < d2.size(); i++ ) {
std::cout << "(" << d2[i].x_ << "," << d2[i].y_ << ") ";
}
std::cout << '\n';
// Initializer List Version = {...} p2 first
std::cout << "Using Array<T>'s = Initializer List with user data type\n";
Array<Point> d3 = { p2, p1, p3 };
for( size_t i = 0; i < d3.size(); i++ ) {
std::cout << "(" << d3[i].x_ << "," << d3[i].y_ << ") ";
}
std::cout << '\n';
// Initializer List Directly p2 first p1 & p3 swapped
std::cout << "Using Array<T>'s Initializer List directly with user data type\n";
Array<Point> d4{ p2, p3, p1 };
for( size_t i = 0; i < d4.size(); i++ ) {
std::cout << "(" << d4[i].x_ << "," << d4[i].y_ << ") ";
}
std::cout << '\n';
std::initializer_list<Point> ilPoints{ p1, p2, p3 };
std::cout << "Using Array<T>'s initializer_list Constructor with user data type\n";
Array<Point> d5( ilPoints );
for( size_t i = 0; i < d5.size(); i++ ) {
std::cout << "(" << d5[i].x_ << "," << d5[i].y_ << ") ";
}
std::cout << "\n\n";
// Need a local copy of the vector instead?
std::cout << "Using Array<T>'s base class's operator()() to retrieve vector\n";
std::vector<Point> points = d4(); // using operator()()
for( auto& p : points ) {
std::cout << "(" << p.x_ << "," << p.y_ << ") ";
}
std::cout << '\n';
// Need a local copy of the pointer instead?
std::cout << "Using Array<T>'s data() to get the contents of its internal pointer\n";
Point* pPoint = nullptr;
pPoint = d4.data();
for( size_t i = 0; i < d4.size(); i++ ) {
std::cout << "(" << pPoint[i].x_ << "," << pPoint[i].y_ << ") ";
}
std::cout << '\n';
} catch( const std::runtime_error& e ) {
std::cerr << e.what() << '\n';
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
-Output-
ParamPack<T> Examples:
Using ParamPack<T>'s Variadic Constructor
Size: 4 | Elements: 1 2 3 4
Using ParamPack<T>'s Variadic Constructor with an Initializer List
Size: 4 | Elements: 5 6 7 8
Using ParamPack<T>'s initializer_list Constructor
Size: 4 | Elements: 9 10 11 12
Array<T> Examples:
Using Array<T>'s initializer_list Constructor
9 10 12 12
Using Array<T>'s Variadic Constructor
9 8 7 6
Hello World
Using Array<T>'s Constructor with Initializer List
105 210 420
Using Array<T>'s Initializer List with =
1 2 3 4
a b c d
1.2 3.4 5.6 7.8
Using Array<T>'s Initializer List directly
3 6 9 12
Using Array<T>'s Variadic Constructor with user data type
(1,2) (3,4) (5,6)
Using Array<T>'s Variadic Constructor With Initializer List of user data type
(5,6) (3,4) (1,2)
Using Array<T>'s = Initializer List with user data type
(3,4) (1,2) (5,6)
Using Array<T>'s Initializer List directly with user data type
(3,4) (5,6) (1,2)
Using Array<T>'s initializer_list Constructor with user data type
Using Array<T>'s base class's operator()() to retrieve vector
(3,4) (5,6) (1,2)
Using Array<T>'s data() to get the contents of its internal pointer
(3,4) (5,6) (1,2)
Теперь это немного более надежно, поскольку в нем есть операторы, доступные как из родительского, так и из дочернего класса, из родительского класса вы можете получить сохраненный vector
непосредственно из его operator()()
. Из дочернего класса вы можете индексировать в сохраненный указатель дочернего элемента из operator[]()
, и есть функция, которая возвращает его размер. Сам шаблон не содержит аргумента шаблона size_t N
, поскольку размер хранится внутри базового класса и определяется размером его вектора. С этим я отношусь к T* p
как к T p[size]
. Этот класс все еще не без некоторых ограничений.
-Действительная конструкция-
Array<int> a( 1, 2, 3, 4 ); // Variadic Constructor Okay
Array<int> a( {1,2,3,4} ); // Initializer List Constructor Okay
Array<int> a = { 1, 2, 3, 4 }; // Initializer List Okay
Array<int> a{ 1,2,3,4 }; // Initializer List Okay
-Limitations-
Однако вы должны explicitly
создать экземпляр шаблона, так как они не будут работать, поскольку все они генерируют ошибку компилятора.
Array a( 1,2,3,4 );
Array a( {1,2,3,4} );
Array a = { 1, 2, 3, 4 };
Array a{ 1,2,3,4 };
-Note- Может быть, можно сделать больше, чтобы сделать это более эффективным, возможно, даже поточно-ориентированным или безопасным для исключений, но это всего лишь обобщение дизайна класса.
Пожалуйста, дайте мне знать, что вы думаете по этому поводу: