You had asked this:
Q: Is there anyway to create an Array class that can be initialized with a braced-init-list without having to manually specify the array length, and without a 'make_array' function.
I have worked on an implementation of a class that behaves in the manner you are describing only it has a little bit more complexity to it but still fairly simple, readable, reusable, portable and generic.
I was not able to have a T items[]
array as a direct member in the class. I had to use a T* items
instead and create an overloaded operator[]
in the derived class to mimic the behavior of an array. This doesn't mean that there isn't any work around to this as others have shown. I just find that this is one possible solution without having to specify the size of the array.
I use a base class to store the elements from the constructor either by means of an std::initializer_list
or of a variadic constructor
. The class templates themselves are not a variadic template only their constructor's are. The base class stores the values from the initializer_list
or parameter pack
into an std::vector
. The inherited class stores the contents from the vector
into T*
by calling the data()
function of the vector class.
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)
Now this is a little more robust as it has operators that are available from both the parent and child class, from the parent class you can get the stored vector
directly from its operator()()
. From the child class you can index into the child's stored pointer from operator[]()
and there is a function to return it's size. The template itself does not contain a size_t N
template argument since the size is stored internally in the base class and is determined by the size of its vector. With this I'm treating T* p
as if it was T p[size]
. This class is still not without some limitations.
-Valid Construction-
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-
However you have to explicitly
instantiate the template as these will not work as they all generate a compiler error.
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- There maybe more that can be done to make this more efficient, maybe even thread safe or exception safe, but this is just a generalization of the class design.
Please let me know what you think about this: