C++ wrapper class to mimic a C array's brace initialization











up vote
5
down vote

favorite












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 Constructorn";
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 Listn";
ParamPack<int> pp2( { 5, 6, 7, 8 } );
std::cout << "Size: " << pp2.size() << " | Elements: ";
for( auto& v : pp2() ) {
std::cout << v << " ";
}
std::cout << "nn";

// Array Examples:
// Using Variadic Constructor
std::cout << "Array<T> Examples:n";
std::cout << "Using Array<T>'s Variadic Constructorn";
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 << "nn";

// Using Constructor w/ Initializer List
std::cout << "Using Array<T>'s Constructor with Initializer Listn";
Array<int> testC( { 105, 210, 420 } );
for( size_t i = 0; i < testC.size(); i++ ) {
std::cout << testC[i] << " ";
}
std::cout << "nn";

// 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 << "nn";

// Using Initializer List directly
std::cout << "Using Array<T>'s Initalizer List directlyn";
Array<uint32_t> a1{ 3, 6, 9, 12 };
for( size_t i = 0; i < a1.size(); i++ ) {
std::cout << a1[i] << " ";
}
std::cout << "nn";

// 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 typen";
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 typen";
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 typen";
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 typen";
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 << "nn";

// Need a local copy of the vector instead?
std::cout << "Using Array<T>'s base class's operator()() to retrieve vectorn";
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 pointern";
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?










share|improve this question
























  • Did you notice that in your ParamPack<T> initializer list test that the size is incorrect?
    – Mike Borkland
    Nov 10 at 22:50










  • @MikeBorkland I sort of know what your are asking, but I'm not sure exactly... I'm not quite grasping where you are inferring that the size is incorrect from the initializer list. I've used both constructors and the size being returned for the number of elements so far as been correct for me.
    – Francis Cugler
    Nov 11 at 7:45










  • @MikeBorkland The code above does not show the initializer_list constructor in use, all the code from main is using the variadic 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.
    – Francis Cugler
    Nov 11 at 7:47












  • @MikeBorkland I think I know what you meant now by the size being wrong. I had changed how the size_ variable was being calculated, instead of calling values_.size() I used size_( 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.
    – Francis Cugler
    Nov 11 at 8:14

















up vote
5
down vote

favorite












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 Constructorn";
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 Listn";
ParamPack<int> pp2( { 5, 6, 7, 8 } );
std::cout << "Size: " << pp2.size() << " | Elements: ";
for( auto& v : pp2() ) {
std::cout << v << " ";
}
std::cout << "nn";

// Array Examples:
// Using Variadic Constructor
std::cout << "Array<T> Examples:n";
std::cout << "Using Array<T>'s Variadic Constructorn";
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 << "nn";

// Using Constructor w/ Initializer List
std::cout << "Using Array<T>'s Constructor with Initializer Listn";
Array<int> testC( { 105, 210, 420 } );
for( size_t i = 0; i < testC.size(); i++ ) {
std::cout << testC[i] << " ";
}
std::cout << "nn";

// 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 << "nn";

// Using Initializer List directly
std::cout << "Using Array<T>'s Initalizer List directlyn";
Array<uint32_t> a1{ 3, 6, 9, 12 };
for( size_t i = 0; i < a1.size(); i++ ) {
std::cout << a1[i] << " ";
}
std::cout << "nn";

// 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 typen";
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 typen";
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 typen";
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 typen";
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 << "nn";

// Need a local copy of the vector instead?
std::cout << "Using Array<T>'s base class's operator()() to retrieve vectorn";
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 pointern";
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?










share|improve this question
























  • Did you notice that in your ParamPack<T> initializer list test that the size is incorrect?
    – Mike Borkland
    Nov 10 at 22:50










  • @MikeBorkland I sort of know what your are asking, but I'm not sure exactly... I'm not quite grasping where you are inferring that the size is incorrect from the initializer list. I've used both constructors and the size being returned for the number of elements so far as been correct for me.
    – Francis Cugler
    Nov 11 at 7:45










  • @MikeBorkland The code above does not show the initializer_list constructor in use, all the code from main is using the variadic 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.
    – Francis Cugler
    Nov 11 at 7:47












  • @MikeBorkland I think I know what you meant now by the size being wrong. I had changed how the size_ variable was being calculated, instead of calling values_.size() I used size_( 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.
    – Francis Cugler
    Nov 11 at 8:14















up vote
5
down vote

favorite









up vote
5
down vote

favorite











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 Constructorn";
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 Listn";
ParamPack<int> pp2( { 5, 6, 7, 8 } );
std::cout << "Size: " << pp2.size() << " | Elements: ";
for( auto& v : pp2() ) {
std::cout << v << " ";
}
std::cout << "nn";

// Array Examples:
// Using Variadic Constructor
std::cout << "Array<T> Examples:n";
std::cout << "Using Array<T>'s Variadic Constructorn";
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 << "nn";

// Using Constructor w/ Initializer List
std::cout << "Using Array<T>'s Constructor with Initializer Listn";
Array<int> testC( { 105, 210, 420 } );
for( size_t i = 0; i < testC.size(); i++ ) {
std::cout << testC[i] << " ";
}
std::cout << "nn";

// 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 << "nn";

// Using Initializer List directly
std::cout << "Using Array<T>'s Initalizer List directlyn";
Array<uint32_t> a1{ 3, 6, 9, 12 };
for( size_t i = 0; i < a1.size(); i++ ) {
std::cout << a1[i] << " ";
}
std::cout << "nn";

// 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 typen";
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 typen";
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 typen";
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 typen";
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 << "nn";

// Need a local copy of the vector instead?
std::cout << "Using Array<T>'s base class's operator()() to retrieve vectorn";
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 pointern";
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?










share|improve this question















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 Constructorn";
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 Listn";
ParamPack<int> pp2( { 5, 6, 7, 8 } );
std::cout << "Size: " << pp2.size() << " | Elements: ";
for( auto& v : pp2() ) {
std::cout << v << " ";
}
std::cout << "nn";

// Array Examples:
// Using Variadic Constructor
std::cout << "Array<T> Examples:n";
std::cout << "Using Array<T>'s Variadic Constructorn";
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 << "nn";

// Using Constructor w/ Initializer List
std::cout << "Using Array<T>'s Constructor with Initializer Listn";
Array<int> testC( { 105, 210, 420 } );
for( size_t i = 0; i < testC.size(); i++ ) {
std::cout << testC[i] << " ";
}
std::cout << "nn";

// 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 << "nn";

// Using Initializer List directly
std::cout << "Using Array<T>'s Initalizer List directlyn";
Array<uint32_t> a1{ 3, 6, 9, 12 };
for( size_t i = 0; i < a1.size(); i++ ) {
std::cout << a1[i] << " ";
}
std::cout << "nn";

// 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 typen";
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 typen";
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 typen";
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 typen";
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 << "nn";

// Need a local copy of the vector instead?
std::cout << "Using Array<T>'s base class's operator()() to retrieve vectorn";
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 pointern";
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?







c++ template c++17 constructor variadic






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 23 at 23:15

























asked Nov 10 at 10:56









Francis Cugler

25116




25116












  • Did you notice that in your ParamPack<T> initializer list test that the size is incorrect?
    – Mike Borkland
    Nov 10 at 22:50










  • @MikeBorkland I sort of know what your are asking, but I'm not sure exactly... I'm not quite grasping where you are inferring that the size is incorrect from the initializer list. I've used both constructors and the size being returned for the number of elements so far as been correct for me.
    – Francis Cugler
    Nov 11 at 7:45










  • @MikeBorkland The code above does not show the initializer_list constructor in use, all the code from main is using the variadic 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.
    – Francis Cugler
    Nov 11 at 7:47












  • @MikeBorkland I think I know what you meant now by the size being wrong. I had changed how the size_ variable was being calculated, instead of calling values_.size() I used size_( 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.
    – Francis Cugler
    Nov 11 at 8:14




















  • Did you notice that in your ParamPack<T> initializer list test that the size is incorrect?
    – Mike Borkland
    Nov 10 at 22:50










  • @MikeBorkland I sort of know what your are asking, but I'm not sure exactly... I'm not quite grasping where you are inferring that the size is incorrect from the initializer list. I've used both constructors and the size being returned for the number of elements so far as been correct for me.
    – Francis Cugler
    Nov 11 at 7:45










  • @MikeBorkland The code above does not show the initializer_list constructor in use, all the code from main is using the variadic 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.
    – Francis Cugler
    Nov 11 at 7:47












  • @MikeBorkland I think I know what you meant now by the size being wrong. I had changed how the size_ variable was being calculated, instead of calling values_.size() I used size_( 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.
    – Francis Cugler
    Nov 11 at 8:14


















Did you notice that in your ParamPack<T> initializer list test that the size is incorrect?
– Mike Borkland
Nov 10 at 22:50




Did you notice that in your ParamPack<T> initializer list test that the size is incorrect?
– Mike Borkland
Nov 10 at 22:50












@MikeBorkland I sort of know what your are asking, but I'm not sure exactly... I'm not quite grasping where you are inferring that the size is incorrect from the initializer list. I've used both constructors and the size being returned for the number of elements so far as been correct for me.
– Francis Cugler
Nov 11 at 7:45




@MikeBorkland I sort of know what your are asking, but I'm not sure exactly... I'm not quite grasping where you are inferring that the size is incorrect from the initializer list. I've used both constructors and the size being returned for the number of elements so far as been correct for me.
– Francis Cugler
Nov 11 at 7:45












@MikeBorkland The code above does not show the initializer_list constructor in use, all the code from main is using the variadic 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.
– Francis Cugler
Nov 11 at 7:47






@MikeBorkland The code above does not show the initializer_list constructor in use, all the code from main is using the variadic 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.
– Francis Cugler
Nov 11 at 7:47














@MikeBorkland I think I know what you meant now by the size being wrong. I had changed how the size_ variable was being calculated, instead of calling values_.size() I used size_( 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.
– Francis Cugler
Nov 11 at 8:14






@MikeBorkland I think I know what you meant now by the size being wrong. I had changed how the size_ variable was being calculated, instead of calling values_.size() I used size_( 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.
– Francis Cugler
Nov 11 at 8:14












2 Answers
2






active

oldest

votes

















up vote
3
down vote














  • I think you should make your interface constexpr

  • Did you expect negative index? If not, try to use std::size_t instead of int.

  • Does returning values_ by value is a design choice?

  • You specify the return type of size () as const size_t but the const is useless here.

  • Try to avoid static_cast when you can.

  • To initialize list_, you can use sizeof... () instead of calling size () method, and adding it at the end of the member initializer lists.

  • Since your parameter pack is know at compile time, have you tried to use std::array instead of std::vector. ( and look at his interface, for a good example of const-correctness.)






share|improve this answer























  • In the ParamPack I changed the function to get the size to just return size_t instead of const size_t. I also changed the operator()() to return std::vector<T>& instead. In Array<T> I changed the operators to take a size_t instead of an int. These were simple easy corrections.
    – Francis Cugler
    Nov 11 at 7:52












  • As for the static_cast in ParamPack<T>'s Variadic Constructor, I've tried removing it and MSVC yells at me, but as soon as I use it all compiler warnings - errors go away.
    – Francis Cugler
    Nov 11 at 7:53










  • As for using std::array instead of std::vector that should not be that hard to modify and I can work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:55










  • I replaced size = values_.size() with size = sizeof...(U) in both constructors.
    – Francis Cugler
    Nov 11 at 7:58










  • The first point you made by making the interface constexpr that might take a little more work as I'm not very family with all of the proper syntax, but I'll continue to work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:58




















up vote
2
down vote













#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <type_traits>
#include <initializer_list>
#include <utility>


Do the following:




  1. Split your test code from your library code.

  2. Organize your headers so it's easier to find if a header has been included.

  3. Remove headers that are not used. (<sstream>, <exception>)




template<typename T>
class ParamPack {
protected:
std::vector<T> values_;
size_t size_;


Using std::vector introduces two problems:




  1. Allocation


    • Data is on the heap instead of the stack.

    • You discarded the size, which is known at compile time, just to be able to derive the size later.



  2. Object Size


    • You are storing


      • the data on the heap,

      • a pointer in Array to the data,

      • three pointers in std::vector<> to the data, and

      • the size of the std::vector<>.



    • A std::array<> would simply store the data on the stack.






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();
}

const size_t size() const { return size_; }


The constructor that takes the initializer list is never taken as substitution failure simply causes it to always be removed from the overload set. In your test code, the implicitly constructed lists are not std::initializer_list<int> types and won't take that overload.



Your arguments in the variadic are passed by value. You end up copying u in Array::Array(...), ParamPack::ParamPack(...), and std::vector<int>::std::vector<int>(std::initializer_list<int>). Read up on perfect forwarding.



Don't store size_. Just call values_.size() whenever you need the size.



Qualify size() with the [[nodiscard]] attribute.



    std::vector<T> operator()() {
return values_;
}


Did you intend to return by value?





    T operator( int idx ) const {
return items_[idx];
}


Did you intend to return by value?



You have an implicit conversion on access std::vector that changes the signedness of the index. std::vector<>::operator expects the index type to be std::vector::size_type, which is often std::size_t.





    int main() { ... }


It is great everything prints out, but are the observed results the expected results? Use a testing framework.






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




Namespace wrapping is definitely a starter. You should minimize the number of symbols you introduce into the global namespace.



Prefer the qualified standard types (::std::size_t) instead of the unqualified types (::size_t). The C++ Standard makes no guarantee that the unqualified symbols exist. See C++17 draft n4659 § 15.5.4.3.4/1.



Use std::array<> when the container has a fixed size known at compile-time.



If you want your container to be used as if it were a C++ container, you must fulfill the standard general container requirements.



Don't use a raw pointer (T*). Use alternative solutions. std::observer_ptr in C++20 doesn't require any special feature that would prevent it from being implemented in earlier C++ revisions. The reason for the existence of the world's dumbest smart pointer is to:




  1. Self-document that the pointer is a non-owning reference to an object (not an owning-reference, built-in array, built-in string or iterator),

  2. Default construct to a sane initial state (nullptr),

  3. Only support reference operations (no operator meant for arrays/strings, no pointer arithmetic meant for pointers),

  4. Strict-weak ordering guarantee, and

  5. Discourages using unsafe void*.



Are there any unforeseen bugs or gotchas that I overlooked?




You should be aware of class template argument deduction guides introduced in C++17. The compiler can deduce both the size and the type from the arguments provided. Here is the deduction guide for std::array.



std::array a1{1, 2, 42};  // deduced as std::array<int, 3>
std::array a2{1, 2, 42u}; // ill-formed as types are not the same.


You can also write your own guide to derive the type while allowing the user to provide a type.






share|improve this answer























  • Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
    – Francis Cugler
    Nov 24 at 5:23











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207357%2fc-wrapper-class-to-mimic-a-c-arrays-brace-initialization%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
3
down vote














  • I think you should make your interface constexpr

  • Did you expect negative index? If not, try to use std::size_t instead of int.

  • Does returning values_ by value is a design choice?

  • You specify the return type of size () as const size_t but the const is useless here.

  • Try to avoid static_cast when you can.

  • To initialize list_, you can use sizeof... () instead of calling size () method, and adding it at the end of the member initializer lists.

  • Since your parameter pack is know at compile time, have you tried to use std::array instead of std::vector. ( and look at his interface, for a good example of const-correctness.)






share|improve this answer























  • In the ParamPack I changed the function to get the size to just return size_t instead of const size_t. I also changed the operator()() to return std::vector<T>& instead. In Array<T> I changed the operators to take a size_t instead of an int. These were simple easy corrections.
    – Francis Cugler
    Nov 11 at 7:52












  • As for the static_cast in ParamPack<T>'s Variadic Constructor, I've tried removing it and MSVC yells at me, but as soon as I use it all compiler warnings - errors go away.
    – Francis Cugler
    Nov 11 at 7:53










  • As for using std::array instead of std::vector that should not be that hard to modify and I can work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:55










  • I replaced size = values_.size() with size = sizeof...(U) in both constructors.
    – Francis Cugler
    Nov 11 at 7:58










  • The first point you made by making the interface constexpr that might take a little more work as I'm not very family with all of the proper syntax, but I'll continue to work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:58

















up vote
3
down vote














  • I think you should make your interface constexpr

  • Did you expect negative index? If not, try to use std::size_t instead of int.

  • Does returning values_ by value is a design choice?

  • You specify the return type of size () as const size_t but the const is useless here.

  • Try to avoid static_cast when you can.

  • To initialize list_, you can use sizeof... () instead of calling size () method, and adding it at the end of the member initializer lists.

  • Since your parameter pack is know at compile time, have you tried to use std::array instead of std::vector. ( and look at his interface, for a good example of const-correctness.)






share|improve this answer























  • In the ParamPack I changed the function to get the size to just return size_t instead of const size_t. I also changed the operator()() to return std::vector<T>& instead. In Array<T> I changed the operators to take a size_t instead of an int. These were simple easy corrections.
    – Francis Cugler
    Nov 11 at 7:52












  • As for the static_cast in ParamPack<T>'s Variadic Constructor, I've tried removing it and MSVC yells at me, but as soon as I use it all compiler warnings - errors go away.
    – Francis Cugler
    Nov 11 at 7:53










  • As for using std::array instead of std::vector that should not be that hard to modify and I can work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:55










  • I replaced size = values_.size() with size = sizeof...(U) in both constructors.
    – Francis Cugler
    Nov 11 at 7:58










  • The first point you made by making the interface constexpr that might take a little more work as I'm not very family with all of the proper syntax, but I'll continue to work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:58















up vote
3
down vote










up vote
3
down vote










  • I think you should make your interface constexpr

  • Did you expect negative index? If not, try to use std::size_t instead of int.

  • Does returning values_ by value is a design choice?

  • You specify the return type of size () as const size_t but the const is useless here.

  • Try to avoid static_cast when you can.

  • To initialize list_, you can use sizeof... () instead of calling size () method, and adding it at the end of the member initializer lists.

  • Since your parameter pack is know at compile time, have you tried to use std::array instead of std::vector. ( and look at his interface, for a good example of const-correctness.)






share|improve this answer















  • I think you should make your interface constexpr

  • Did you expect negative index? If not, try to use std::size_t instead of int.

  • Does returning values_ by value is a design choice?

  • You specify the return type of size () as const size_t but the const is useless here.

  • Try to avoid static_cast when you can.

  • To initialize list_, you can use sizeof... () instead of calling size () method, and adding it at the end of the member initializer lists.

  • Since your parameter pack is know at compile time, have you tried to use std::array instead of std::vector. ( and look at his interface, for a good example of const-correctness.)







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 10 at 22:15

























answered Nov 10 at 19:59









Calak

2,001314




2,001314












  • In the ParamPack I changed the function to get the size to just return size_t instead of const size_t. I also changed the operator()() to return std::vector<T>& instead. In Array<T> I changed the operators to take a size_t instead of an int. These were simple easy corrections.
    – Francis Cugler
    Nov 11 at 7:52












  • As for the static_cast in ParamPack<T>'s Variadic Constructor, I've tried removing it and MSVC yells at me, but as soon as I use it all compiler warnings - errors go away.
    – Francis Cugler
    Nov 11 at 7:53










  • As for using std::array instead of std::vector that should not be that hard to modify and I can work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:55










  • I replaced size = values_.size() with size = sizeof...(U) in both constructors.
    – Francis Cugler
    Nov 11 at 7:58










  • The first point you made by making the interface constexpr that might take a little more work as I'm not very family with all of the proper syntax, but I'll continue to work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:58




















  • In the ParamPack I changed the function to get the size to just return size_t instead of const size_t. I also changed the operator()() to return std::vector<T>& instead. In Array<T> I changed the operators to take a size_t instead of an int. These were simple easy corrections.
    – Francis Cugler
    Nov 11 at 7:52












  • As for the static_cast in ParamPack<T>'s Variadic Constructor, I've tried removing it and MSVC yells at me, but as soon as I use it all compiler warnings - errors go away.
    – Francis Cugler
    Nov 11 at 7:53










  • As for using std::array instead of std::vector that should not be that hard to modify and I can work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:55










  • I replaced size = values_.size() with size = sizeof...(U) in both constructors.
    – Francis Cugler
    Nov 11 at 7:58










  • The first point you made by making the interface constexpr that might take a little more work as I'm not very family with all of the proper syntax, but I'll continue to work on that at a later time.
    – Francis Cugler
    Nov 11 at 7:58


















In the ParamPack I changed the function to get the size to just return size_t instead of const size_t. I also changed the operator()() to return std::vector<T>& instead. In Array<T> I changed the operators to take a size_t instead of an int. These were simple easy corrections.
– Francis Cugler
Nov 11 at 7:52






In the ParamPack I changed the function to get the size to just return size_t instead of const size_t. I also changed the operator()() to return std::vector<T>& instead. In Array<T> I changed the operators to take a size_t instead of an int. These were simple easy corrections.
– Francis Cugler
Nov 11 at 7:52














As for the static_cast in ParamPack<T>'s Variadic Constructor, I've tried removing it and MSVC yells at me, but as soon as I use it all compiler warnings - errors go away.
– Francis Cugler
Nov 11 at 7:53




As for the static_cast in ParamPack<T>'s Variadic Constructor, I've tried removing it and MSVC yells at me, but as soon as I use it all compiler warnings - errors go away.
– Francis Cugler
Nov 11 at 7:53












As for using std::array instead of std::vector that should not be that hard to modify and I can work on that at a later time.
– Francis Cugler
Nov 11 at 7:55




As for using std::array instead of std::vector that should not be that hard to modify and I can work on that at a later time.
– Francis Cugler
Nov 11 at 7:55












I replaced size = values_.size() with size = sizeof...(U) in both constructors.
– Francis Cugler
Nov 11 at 7:58




I replaced size = values_.size() with size = sizeof...(U) in both constructors.
– Francis Cugler
Nov 11 at 7:58












The first point you made by making the interface constexpr that might take a little more work as I'm not very family with all of the proper syntax, but I'll continue to work on that at a later time.
– Francis Cugler
Nov 11 at 7:58






The first point you made by making the interface constexpr that might take a little more work as I'm not very family with all of the proper syntax, but I'll continue to work on that at a later time.
– Francis Cugler
Nov 11 at 7:58














up vote
2
down vote













#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <type_traits>
#include <initializer_list>
#include <utility>


Do the following:




  1. Split your test code from your library code.

  2. Organize your headers so it's easier to find if a header has been included.

  3. Remove headers that are not used. (<sstream>, <exception>)




template<typename T>
class ParamPack {
protected:
std::vector<T> values_;
size_t size_;


Using std::vector introduces two problems:




  1. Allocation


    • Data is on the heap instead of the stack.

    • You discarded the size, which is known at compile time, just to be able to derive the size later.



  2. Object Size


    • You are storing


      • the data on the heap,

      • a pointer in Array to the data,

      • three pointers in std::vector<> to the data, and

      • the size of the std::vector<>.



    • A std::array<> would simply store the data on the stack.






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();
}

const size_t size() const { return size_; }


The constructor that takes the initializer list is never taken as substitution failure simply causes it to always be removed from the overload set. In your test code, the implicitly constructed lists are not std::initializer_list<int> types and won't take that overload.



Your arguments in the variadic are passed by value. You end up copying u in Array::Array(...), ParamPack::ParamPack(...), and std::vector<int>::std::vector<int>(std::initializer_list<int>). Read up on perfect forwarding.



Don't store size_. Just call values_.size() whenever you need the size.



Qualify size() with the [[nodiscard]] attribute.



    std::vector<T> operator()() {
return values_;
}


Did you intend to return by value?





    T operator( int idx ) const {
return items_[idx];
}


Did you intend to return by value?



You have an implicit conversion on access std::vector that changes the signedness of the index. std::vector<>::operator expects the index type to be std::vector::size_type, which is often std::size_t.





    int main() { ... }


It is great everything prints out, but are the observed results the expected results? Use a testing framework.






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




Namespace wrapping is definitely a starter. You should minimize the number of symbols you introduce into the global namespace.



Prefer the qualified standard types (::std::size_t) instead of the unqualified types (::size_t). The C++ Standard makes no guarantee that the unqualified symbols exist. See C++17 draft n4659 § 15.5.4.3.4/1.



Use std::array<> when the container has a fixed size known at compile-time.



If you want your container to be used as if it were a C++ container, you must fulfill the standard general container requirements.



Don't use a raw pointer (T*). Use alternative solutions. std::observer_ptr in C++20 doesn't require any special feature that would prevent it from being implemented in earlier C++ revisions. The reason for the existence of the world's dumbest smart pointer is to:




  1. Self-document that the pointer is a non-owning reference to an object (not an owning-reference, built-in array, built-in string or iterator),

  2. Default construct to a sane initial state (nullptr),

  3. Only support reference operations (no operator meant for arrays/strings, no pointer arithmetic meant for pointers),

  4. Strict-weak ordering guarantee, and

  5. Discourages using unsafe void*.



Are there any unforeseen bugs or gotchas that I overlooked?




You should be aware of class template argument deduction guides introduced in C++17. The compiler can deduce both the size and the type from the arguments provided. Here is the deduction guide for std::array.



std::array a1{1, 2, 42};  // deduced as std::array<int, 3>
std::array a2{1, 2, 42u}; // ill-formed as types are not the same.


You can also write your own guide to derive the type while allowing the user to provide a type.






share|improve this answer























  • Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
    – Francis Cugler
    Nov 24 at 5:23















up vote
2
down vote













#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <type_traits>
#include <initializer_list>
#include <utility>


Do the following:




  1. Split your test code from your library code.

  2. Organize your headers so it's easier to find if a header has been included.

  3. Remove headers that are not used. (<sstream>, <exception>)




template<typename T>
class ParamPack {
protected:
std::vector<T> values_;
size_t size_;


Using std::vector introduces two problems:




  1. Allocation


    • Data is on the heap instead of the stack.

    • You discarded the size, which is known at compile time, just to be able to derive the size later.



  2. Object Size


    • You are storing


      • the data on the heap,

      • a pointer in Array to the data,

      • three pointers in std::vector<> to the data, and

      • the size of the std::vector<>.



    • A std::array<> would simply store the data on the stack.






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();
}

const size_t size() const { return size_; }


The constructor that takes the initializer list is never taken as substitution failure simply causes it to always be removed from the overload set. In your test code, the implicitly constructed lists are not std::initializer_list<int> types and won't take that overload.



Your arguments in the variadic are passed by value. You end up copying u in Array::Array(...), ParamPack::ParamPack(...), and std::vector<int>::std::vector<int>(std::initializer_list<int>). Read up on perfect forwarding.



Don't store size_. Just call values_.size() whenever you need the size.



Qualify size() with the [[nodiscard]] attribute.



    std::vector<T> operator()() {
return values_;
}


Did you intend to return by value?





    T operator( int idx ) const {
return items_[idx];
}


Did you intend to return by value?



You have an implicit conversion on access std::vector that changes the signedness of the index. std::vector<>::operator expects the index type to be std::vector::size_type, which is often std::size_t.





    int main() { ... }


It is great everything prints out, but are the observed results the expected results? Use a testing framework.






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




Namespace wrapping is definitely a starter. You should minimize the number of symbols you introduce into the global namespace.



Prefer the qualified standard types (::std::size_t) instead of the unqualified types (::size_t). The C++ Standard makes no guarantee that the unqualified symbols exist. See C++17 draft n4659 § 15.5.4.3.4/1.



Use std::array<> when the container has a fixed size known at compile-time.



If you want your container to be used as if it were a C++ container, you must fulfill the standard general container requirements.



Don't use a raw pointer (T*). Use alternative solutions. std::observer_ptr in C++20 doesn't require any special feature that would prevent it from being implemented in earlier C++ revisions. The reason for the existence of the world's dumbest smart pointer is to:




  1. Self-document that the pointer is a non-owning reference to an object (not an owning-reference, built-in array, built-in string or iterator),

  2. Default construct to a sane initial state (nullptr),

  3. Only support reference operations (no operator meant for arrays/strings, no pointer arithmetic meant for pointers),

  4. Strict-weak ordering guarantee, and

  5. Discourages using unsafe void*.



Are there any unforeseen bugs or gotchas that I overlooked?




You should be aware of class template argument deduction guides introduced in C++17. The compiler can deduce both the size and the type from the arguments provided. Here is the deduction guide for std::array.



std::array a1{1, 2, 42};  // deduced as std::array<int, 3>
std::array a2{1, 2, 42u}; // ill-formed as types are not the same.


You can also write your own guide to derive the type while allowing the user to provide a type.






share|improve this answer























  • Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
    – Francis Cugler
    Nov 24 at 5:23













up vote
2
down vote










up vote
2
down vote









#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <type_traits>
#include <initializer_list>
#include <utility>


Do the following:




  1. Split your test code from your library code.

  2. Organize your headers so it's easier to find if a header has been included.

  3. Remove headers that are not used. (<sstream>, <exception>)




template<typename T>
class ParamPack {
protected:
std::vector<T> values_;
size_t size_;


Using std::vector introduces two problems:




  1. Allocation


    • Data is on the heap instead of the stack.

    • You discarded the size, which is known at compile time, just to be able to derive the size later.



  2. Object Size


    • You are storing


      • the data on the heap,

      • a pointer in Array to the data,

      • three pointers in std::vector<> to the data, and

      • the size of the std::vector<>.



    • A std::array<> would simply store the data on the stack.






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();
}

const size_t size() const { return size_; }


The constructor that takes the initializer list is never taken as substitution failure simply causes it to always be removed from the overload set. In your test code, the implicitly constructed lists are not std::initializer_list<int> types and won't take that overload.



Your arguments in the variadic are passed by value. You end up copying u in Array::Array(...), ParamPack::ParamPack(...), and std::vector<int>::std::vector<int>(std::initializer_list<int>). Read up on perfect forwarding.



Don't store size_. Just call values_.size() whenever you need the size.



Qualify size() with the [[nodiscard]] attribute.



    std::vector<T> operator()() {
return values_;
}


Did you intend to return by value?





    T operator( int idx ) const {
return items_[idx];
}


Did you intend to return by value?



You have an implicit conversion on access std::vector that changes the signedness of the index. std::vector<>::operator expects the index type to be std::vector::size_type, which is often std::size_t.





    int main() { ... }


It is great everything prints out, but are the observed results the expected results? Use a testing framework.






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




Namespace wrapping is definitely a starter. You should minimize the number of symbols you introduce into the global namespace.



Prefer the qualified standard types (::std::size_t) instead of the unqualified types (::size_t). The C++ Standard makes no guarantee that the unqualified symbols exist. See C++17 draft n4659 § 15.5.4.3.4/1.



Use std::array<> when the container has a fixed size known at compile-time.



If you want your container to be used as if it were a C++ container, you must fulfill the standard general container requirements.



Don't use a raw pointer (T*). Use alternative solutions. std::observer_ptr in C++20 doesn't require any special feature that would prevent it from being implemented in earlier C++ revisions. The reason for the existence of the world's dumbest smart pointer is to:




  1. Self-document that the pointer is a non-owning reference to an object (not an owning-reference, built-in array, built-in string or iterator),

  2. Default construct to a sane initial state (nullptr),

  3. Only support reference operations (no operator meant for arrays/strings, no pointer arithmetic meant for pointers),

  4. Strict-weak ordering guarantee, and

  5. Discourages using unsafe void*.



Are there any unforeseen bugs or gotchas that I overlooked?




You should be aware of class template argument deduction guides introduced in C++17. The compiler can deduce both the size and the type from the arguments provided. Here is the deduction guide for std::array.



std::array a1{1, 2, 42};  // deduced as std::array<int, 3>
std::array a2{1, 2, 42u}; // ill-formed as types are not the same.


You can also write your own guide to derive the type while allowing the user to provide a type.






share|improve this answer














#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <type_traits>
#include <initializer_list>
#include <utility>


Do the following:




  1. Split your test code from your library code.

  2. Organize your headers so it's easier to find if a header has been included.

  3. Remove headers that are not used. (<sstream>, <exception>)




template<typename T>
class ParamPack {
protected:
std::vector<T> values_;
size_t size_;


Using std::vector introduces two problems:




  1. Allocation


    • Data is on the heap instead of the stack.

    • You discarded the size, which is known at compile time, just to be able to derive the size later.



  2. Object Size


    • You are storing


      • the data on the heap,

      • a pointer in Array to the data,

      • three pointers in std::vector<> to the data, and

      • the size of the std::vector<>.



    • A std::array<> would simply store the data on the stack.






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();
}

const size_t size() const { return size_; }


The constructor that takes the initializer list is never taken as substitution failure simply causes it to always be removed from the overload set. In your test code, the implicitly constructed lists are not std::initializer_list<int> types and won't take that overload.



Your arguments in the variadic are passed by value. You end up copying u in Array::Array(...), ParamPack::ParamPack(...), and std::vector<int>::std::vector<int>(std::initializer_list<int>). Read up on perfect forwarding.



Don't store size_. Just call values_.size() whenever you need the size.



Qualify size() with the [[nodiscard]] attribute.



    std::vector<T> operator()() {
return values_;
}


Did you intend to return by value?





    T operator( int idx ) const {
return items_[idx];
}


Did you intend to return by value?



You have an implicit conversion on access std::vector that changes the signedness of the index. std::vector<>::operator expects the index type to be std::vector::size_type, which is often std::size_t.





    int main() { ... }


It is great everything prints out, but are the observed results the expected results? Use a testing framework.






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




Namespace wrapping is definitely a starter. You should minimize the number of symbols you introduce into the global namespace.



Prefer the qualified standard types (::std::size_t) instead of the unqualified types (::size_t). The C++ Standard makes no guarantee that the unqualified symbols exist. See C++17 draft n4659 § 15.5.4.3.4/1.



Use std::array<> when the container has a fixed size known at compile-time.



If you want your container to be used as if it were a C++ container, you must fulfill the standard general container requirements.



Don't use a raw pointer (T*). Use alternative solutions. std::observer_ptr in C++20 doesn't require any special feature that would prevent it from being implemented in earlier C++ revisions. The reason for the existence of the world's dumbest smart pointer is to:




  1. Self-document that the pointer is a non-owning reference to an object (not an owning-reference, built-in array, built-in string or iterator),

  2. Default construct to a sane initial state (nullptr),

  3. Only support reference operations (no operator meant for arrays/strings, no pointer arithmetic meant for pointers),

  4. Strict-weak ordering guarantee, and

  5. Discourages using unsafe void*.



Are there any unforeseen bugs or gotchas that I overlooked?




You should be aware of class template argument deduction guides introduced in C++17. The compiler can deduce both the size and the type from the arguments provided. Here is the deduction guide for std::array.



std::array a1{1, 2, 42};  // deduced as std::array<int, 3>
std::array a2{1, 2, 42u}; // ill-formed as types are not the same.


You can also write your own guide to derive the type while allowing the user to provide a type.







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 24 at 4:02

























answered Nov 24 at 3:28









Snowhawk

5,15911028




5,15911028












  • Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
    – Francis Cugler
    Nov 24 at 5:23


















  • Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
    – Francis Cugler
    Nov 24 at 5:23
















Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
– Francis Cugler
Nov 24 at 5:23




Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
– Francis Cugler
Nov 24 at 5:23


















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207357%2fc-wrapper-class-to-mimic-a-c-arrays-brace-initialization%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Сан-Квентин

Алькесар

Josef Freinademetz