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?
c++ template c++17 constructor variadic
add a comment |
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?
c++ template c++17 constructor variadic
Did you notice that in yourParamPack<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 theinitializer_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.
– 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 thesize_
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.
– Francis Cugler
Nov 11 at 8:14
add a comment |
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?
c++ template c++17 constructor variadic
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
c++ template c++17 constructor variadic
edited Nov 23 at 23:15
asked Nov 10 at 10:56
Francis Cugler
25116
25116
Did you notice that in yourParamPack<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 theinitializer_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.
– 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 thesize_
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.
– Francis Cugler
Nov 11 at 8:14
add a comment |
Did you notice that in yourParamPack<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 theinitializer_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.
– 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 thesize_
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.
– 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
add a comment |
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 ()
asconst size_t
but the const is useless here. - Try to avoid
static_cast
when you can. - To initialize
list_
, you can usesizeof... ()
instead of callingsize ()
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 ofstd::vector
. ( and look at his interface, for a good example of const-correctness.)
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 replacedsize = values_.size()
withsize = sizeof...(U)
in both constructors.
– Francis Cugler
Nov 11 at 7:58
The first point you made by making the interfaceconstexpr
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
|
show 1 more comment
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:
- Split your test code from your library code.
- Organize your headers so it's easier to find if a header has been included.
- 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:
- 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.
- 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.
- You are storing
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:
- 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),
- Default construct to a sane initial state (
nullptr
), - Only support reference operations (no
operator
meant for arrays/strings, no pointer arithmetic meant for pointers), - Strict-weak ordering guarantee, and
- 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.
Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
– Francis Cugler
Nov 24 at 5:23
add a comment |
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 ()
asconst size_t
but the const is useless here. - Try to avoid
static_cast
when you can. - To initialize
list_
, you can usesizeof... ()
instead of callingsize ()
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 ofstd::vector
. ( and look at his interface, for a good example of const-correctness.)
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 replacedsize = values_.size()
withsize = sizeof...(U)
in both constructors.
– Francis Cugler
Nov 11 at 7:58
The first point you made by making the interfaceconstexpr
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
|
show 1 more comment
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 ()
asconst size_t
but the const is useless here. - Try to avoid
static_cast
when you can. - To initialize
list_
, you can usesizeof... ()
instead of callingsize ()
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 ofstd::vector
. ( and look at his interface, for a good example of const-correctness.)
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 replacedsize = values_.size()
withsize = sizeof...(U)
in both constructors.
– Francis Cugler
Nov 11 at 7:58
The first point you made by making the interfaceconstexpr
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
|
show 1 more comment
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 ()
asconst size_t
but the const is useless here. - Try to avoid
static_cast
when you can. - To initialize
list_
, you can usesizeof... ()
instead of callingsize ()
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 ofstd::vector
. ( and look at his interface, for a good example of const-correctness.)
- 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 ()
asconst size_t
but the const is useless here. - Try to avoid
static_cast
when you can. - To initialize
list_
, you can usesizeof... ()
instead of callingsize ()
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 ofstd::vector
. ( and look at his interface, for a good example of const-correctness.)
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 replacedsize = values_.size()
withsize = sizeof...(U)
in both constructors.
– Francis Cugler
Nov 11 at 7:58
The first point you made by making the interfaceconstexpr
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
|
show 1 more comment
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 replacedsize = values_.size()
withsize = sizeof...(U)
in both constructors.
– Francis Cugler
Nov 11 at 7:58
The first point you made by making the interfaceconstexpr
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
|
show 1 more comment
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:
- Split your test code from your library code.
- Organize your headers so it's easier to find if a header has been included.
- 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:
- 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.
- 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.
- You are storing
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:
- 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),
- Default construct to a sane initial state (
nullptr
), - Only support reference operations (no
operator
meant for arrays/strings, no pointer arithmetic meant for pointers), - Strict-weak ordering guarantee, and
- 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.
Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
– Francis Cugler
Nov 24 at 5:23
add a comment |
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:
- Split your test code from your library code.
- Organize your headers so it's easier to find if a header has been included.
- 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:
- 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.
- 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.
- You are storing
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:
- 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),
- Default construct to a sane initial state (
nullptr
), - Only support reference operations (no
operator
meant for arrays/strings, no pointer arithmetic meant for pointers), - Strict-weak ordering guarantee, and
- 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.
Very nice write up with concise yet detailed explanations. I truly appreciate your time and effort.
– Francis Cugler
Nov 24 at 5:23
add a comment |
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:
- Split your test code from your library code.
- Organize your headers so it's easier to find if a header has been included.
- 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:
- 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.
- 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.
- You are storing
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:
- 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),
- Default construct to a sane initial state (
nullptr
), - Only support reference operations (no
operator
meant for arrays/strings, no pointer arithmetic meant for pointers), - Strict-weak ordering guarantee, and
- 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.
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <type_traits>
#include <initializer_list>
#include <utility>
Do the following:
- Split your test code from your library code.
- Organize your headers so it's easier to find if a header has been included.
- 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:
- 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.
- 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.
- You are storing
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:
- 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),
- Default construct to a sane initial state (
nullptr
), - Only support reference operations (no
operator
meant for arrays/strings, no pointer arithmetic meant for pointers), - Strict-weak ordering guarantee, and
- 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.
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
add a comment |
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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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 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.– 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 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.– Francis Cugler
Nov 11 at 8:14