gslcpp
Modern-C++ Wrapper for GSL

The template-class gsl::vector is one of two descendants of gsl::v_iface.

The other is gsl::vector_view.

gsl::vector exists to provide constructors for gsl::v_iface.

  • gsl::vector provides no interface other than construction.
  • gsl::v_iface provides almost all of the interfaces that GSL's native vector provides.
  • Any instance constructed via one of the template-specializations of gsl::vector owns memory allocated both for the elements in the vector and for some metadata referring to that memory.

Dynamic Allocation of Memory

For any element-type T (double, float, complex<long double>, etc.), any instance of gsl::vector<T,0> has the number of elements determined at run-time.

  • Internally, construction involves a call
    • to gsl_vector_alloc() or
    • to gsl_vector_calloc().
  • The pointer returned is stored on the stack inside the instance.
  • There is also on the stack a pointer to the virtual-function table.
    • The ultimate base-class gsl::v_stor<T>, which manages the pointer, has a virtual destructor.

Static Allocation of Memory

For any element-type T and positive compile-time integer N, any instance of gsl::vector<T,N> has the number of elements determined at compile-time.

  • Internally, the elements are allocated on the stack in a C-style array.
  • The return-value of gsl_vector_view_array() is also stored on the stack in the instance of gsl::vector.
    • The instance of gsl_vector_view returned by gsl_vector_view_array() consumes about 40 bytes on a 64-bit machine.
  • There is on the stack no pointer to the virtual-function table.
    • There is no virtual destructor in the inheritance-chain.

Example

The template-parameters to gsl::vector often need not be specified because they are deduced by the compiler.

Here is an example-program:

using std::cout;
using std::endl;
// Example-usage of gsl::vector.
int main() {
complex<float> const a1[]= {{1.0f, -1.0f}, {0.5f, 0.6f}};
// Deduce vector<complex<float>,2> and copy from array.
vector v1= a1;
cout << "\n"
<< "v1 has, on stack, elements and internal vector-view.\n"
<< "Instance of gsl_vector_view consumes 40 bytes on 64-bit hardware.\n"
<< "sizeof(v1)=" << sizeof(v1) << " v1=" << v1 << "\n"
<< "v1.get(1)=" << v1.get(1) << endl;
// Deduce vector<double,5>, and copy from initializer-list.
vector v2({1.0, 3.0, 5.0, 7.0, 9.0});
cout << "\n"
<< "v2 also has, on stack, elements and internal vector-view.\n"
<< "sizeof(v2)=" << sizeof(v2) << " v2=" << v2 << "\n"
<< "v2.max()=" << v2.max() << endl;
// Deduce vector<double,0>, set size to four, and initialize elements to
// zero. Optional argument CALLOC causes initialization to zero.
vector<double> v3(4, CALLOC);
cout << "\n"
<< "v3 has, on stack, pointer to vtable and pointer to gsl_vector.\n"
<< "Pointer to vtable is stored because dynamic vector (with compile-\n"
<< "time size of 0) has virtual destructor.\n"
<< "sizeof(v3)=" << sizeof(v3) << " v3=" << v3 << "\n"
<< "v3.isnull()=" << v3.isnull() << endl;
float a4[]= {1, 2, 3, 4, 5, 6};
// Deduce vector<float,0>, set size to four, and copy first four elements
// from array. The output contains a subvector of v4: length 2, starting at
// offset 1 into v4, and with stride 2 relative to v4.
vector v4(a4, 4);
cout << "\n"
<< "v4 also has, on stack, ptr to vtable and ptr to gsl_vector.\n"
<< "sizeof(v4)=" << sizeof(v4) << " v4=" << v4 << "\n"
<< "v4.subvector(2, 1, 2)=" << v4.subvector(2, 1, 2) << endl;
short a5[]= {1, 2, 3, 4, 5, 6};
// Deduce vector<short,0>, set size to 3, and copy first, third, and fifth
// elements from array. Optional third argument to constructor is stride.
vector v5(a5, 3, 2);
cout << "\n"
<< "v5 also has, on stack, ptr to vtable and ptr to gsl_vector.\n"
<< "sizeof(v5)=" << sizeof(v5) << " v5=" << v5 << "\n"
<< "v5.max_index()=" << v5.max_index() << endl;
}

And here is its output:

v1 has, on stack, elements and internal vector-view.
Instance of gsl_vector_view consumes 40 bytes on 64-bit hardware.
sizeof(v1)=56 v1=[(1,-1),(0.5,0.6)]
v1.get(1)=(0.5,0.6)
v2 also has, on stack, elements and internal vector-view.
sizeof(v2)=16 v2=[1,3,5,7,9]
v2.max()=9
v3 has, on stack, pointer to vtable and pointer to gsl_vector.
Pointer to vtable is stored because dynamic vector (with compile-
time size of 0) has virtual destructor.
sizeof(v3)=16 v3=[0,0,0,0]
v3.isnull()=1
v4 also has, on stack, ptr to vtable and ptr to gsl_vector.
sizeof(v4)=16 v4=[1,2,3,4]
v4.subvector(2, 1, 2)=[2,4]
v5 also has, on stack, ptr to vtable and ptr to gsl_vector.
sizeof(v5)=16 v5=[1,3,5]
v5.max_index()=2

Move-Construction

The move-constructor is also defined for gsl::vector<T>, and so one could also do something like this:

#include <gsl/vector.hpp>
vector<float,0> read_big_file_into_array();
int main() {
vector v= read_big_file_into_array();
return 0;
}

In compiling the code above, the compiler,

  • might elide the constructor-call in the installation of the return-value from read_big_file_into_array() into v, but
  • is not required to elide the call (according to C++-17), depending on how the vector inside the function is obtained.

However, even if the compiler do not elide the call to a constructor, the big array would still not be copied into v because there is a move-constructor that would be called.

vector.hpp
Definition for gsl::vector.
gsl::v_iface::get
T get(size_t i) const
Read element with bounds-checking.
Definition: v-iface.hpp:116
gsl::complex
Complex-number, each of whose components has type A.
Definition: complex.hpp:18
gsl::CALLOC
@ CALLOC
Initialize each element to zero after allocation.
Definition: v-stor.hpp:52
gsl::vector
Constructor-type for vector whose storage is owned by instance of vector.
Definition: vector.hpp:67