I have an inheritance class that mimics the behavior of C style array's brace initialization by using a class template that has a variadic constructor but the template itself is not a variadic template. The base class stores the contents from two flavors of constructors either a Variadic Constructor, or a Variadic Constructor that takes an std::initializer_list
as it's parameter. The base class also contains a size that is inferred by its populated member vector, a function to return its size and an overloaded operator operator()()
that will return its internal vector.
The child class inherits this functionality. It only has one member and that is a pointer to <T>
. It has the corresponding matching constructors as its parent class, a set of overloaded subscript-indexing operators and a public method to retrieve it's internal pointer. This class's internal pointer is set to point to its parent's internal vector's data.
As far as I can tell this appears to be bug free to the best of my knowledge and it is producing expected values. Here is what I have so far.
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <type_traits>
#include <initializer_list>
#include <utility>
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_ = values_.size();
}
template<typename ... U>
ParamPack( std::initializer_list<std::is_same<T, U...>( U...)> il ) : values_( il ) {
size_ = values_.size();
}
std::vector<T> operator()() {
return values_;
}
const 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[]( int idx ) {
return items_[idx];
}
T operator[]( int 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::initializer_list<int> il{ 1,2,3,4 };
std::cout << "Using ParamPack<T>'s 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\n";
// Array Examples:
// Using Variadic Constructor
std::cout << "Array<T> Examples:\n";
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 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\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';
// Will Not Compile: Must Be Instantiated By Type Explicitly
// Array e( 1, 2, 3, 4 );
// Array e( { 1,2,3,4 } );
// Array f{ 1, 2, 3, 4 };
// Array g = { 1,2,3,4 };
} 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 Initializer List Size: 5 | Elements: 5 6 7 8 Array<T> Examples: 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 Initializer List Constructor with 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 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)
There are few things I'd like to know about this code: The main concerns are more towards the classes themselves than the actual code use within the main function. However, suggestions towards the use of the class are accepted as well.
- Does this follow modern best practices? Note: I know I can encapsulate it in an
namespace
but for simplicity I just posted the classes themselves- If not what changes or fixes need to be made?
- Is it considered readable enough?
- Is the code efficient?
- What can be done to improve its efficiency?
- Are there any unforeseen bugs or gotchas that I overlooked?
- Is this considered portable, reusable, and generic?
- Will the derived class invoke the appropriate constructor or will it just fall back to one of them?
ParamPack<T>
initializer list test that the size is incorrect? \$\endgroup\$initializer_list
constructor in use, all the code from main is using thevariadic
constructor. To simply use the initializer_list version, just create an initializer_list beforehand and pass that into the constructor. I've tried many examples and nothing has crashed and I'm getting a code of(0)
when the program exits. \$\endgroup\$size_
variable was being calculated, instead of callingvalues_.size()
I usedsize_( sizeof...(U) )
in the constructors initializer list. From here I noticed that when I used the constructor that takes an initializer list it was reporting size of 1 and not the size of the elements. So for the 2nd constructor I now calculate it like:size_ = il.size();
within the constructor. I might move this to the constructors initializer list. \$\endgroup\$