Skip to content

Latest commit

 

History

History
1120 lines (872 loc) · 31.2 KB

Interfacing-C-APIs-and-libraries.org

File metadata and controls

1120 lines (872 loc) · 31.2 KB

CPP / C++ Notes - Integration with C-APIs

Interfacing C APIs and libraries

Outline

Coverage:

  • Design C++ wrappers for C APIs
  • Call C code from C++
  • Call Fortran from C++
  • Call C++ from C and from other programming language C-APIs.
  • Create C wrappers for C++ code C++ libraries.

Definitions

  • Nix like: Any operating system based or inspiered on UNIX (Tradermark of Opengroup) such as Linux, Android, BSD, MacOX and so on. No operating system can call itself UNIX without the Opengroup certification.
  • GSL - GNU Scientifc Library

Widely Used C Functions

Overview

Those functions appear quite often in C code and old C++ codes, thus it is important understanding them for accessing or consuming or C APIs and also creating C++ wrappers for those libraries.

Header: for C++ - <cstring>, for C - <string.h>

  • “String” (character array) Manipulation
    • strcat - Concatenate strings (const char*)
    • strcpy - Copy strings
    • strcmp - Compare strings
  • Memory/buffer manipulation - Header <string.H>
    • memset - Fill memory
    • memcpy - Copy memory
    • memmove - Move data from one location to another

string manipulation

strlen

  • strlen
    • Get the size of a null-terminated (terminated with null char’\0’) array of characters.

Signature:

#include <string.h>
size_t strlen(const char *s);

Usage:

>> const char* str1 = nullptr;

>> strlen(str1);
 ** Segmentatio Fault *** 

>> const char* str2 = "Testing C++ on Linux operating system";
>> 
>> strlen(str2)
(unsigned long) 37
>> 

>> char str3 [] = { 'x', 'y', 'z', 'w'};
>> str3
(char [4]) "xyzw"
>> 
>> strlen(str3)
(unsigned long) 4
>> 

strcmp

  • strcmp
    • Compare two C-strings returning 0 if they are equal.

Signature:

int strcmp(const char *s1, const char *s2);

Example:

#include <cstring> // Or in C: #include <string.h>

>> strcmp("hello", "hello")
(int) 0
>> 
>> strcmp("hello world", "hello")
(int) 32

// If strings are equal, the function returns 0.
>> strcmp("hello world", "hello world")
(int) 0

>> strcmp("hello world", "hello wo")
(int) 114
>> 
>> if(!strcmp("hello world", "hello world")){ std::puts("equal"); }
equal
>> if(!strcmp("hello wo", "hello world")){ std::puts("equal"); }
>>  

strcat

  • strcat
    • Append string src (null terminated character array, terminated at \0) to the dest string.
    • WARNING: “If dest is not large enough, program behavior is unpredictable; buffer overruns are a favorite avenue for attacking secure programs.”
    • Return: It returns the pointer to the dest array.

Signature:

char *strcat(char *dest, const char *src);

Example:

#include <cstring> // In C: #include <string.h>

>> char str [200] = "Hello world C++ " ;
>> str
(char [200]) "Hello world C++ \0\0\0\0\0\0\0\0\0..."

>> strcat(str, " C++11")
(char *) "Hello world C++  C++11"
>> strcat(str, " C++14")
(char *) "Hello world C++  C++11 C++14"
>> strcat(str, " C++17")
(char *) "Hello world C++  C++11 C++14 C++17"
>> 

>> str
(char [200]) "Hello world C++  C++11 C++14 C++17\0\0\0\.."

>> 
>> std::cout << "str = " << str << "\n";
str = Hello world C++  C++11 C++14 C++17
>> 

strcpy

  • strcpy
    • Copies the string pointed by src (null-terminated char array) to the buffer pointed by dest.
    • WARNING: The strings may not overlap, and the destination string dest must be large enough to receive the copy. Beware of buffer overruns! (See BUGS.)

Signature:

char *strcpy(char *dest, const char *src);

Example:

#include <cstring> // In C: #include <string.h>

// Usage example: 
char arr[200]; 

>> char arr[200]; 
>> arr
(char [200]) "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\..."

>> strcpy(arr, "Interfacing C APIs and libraries")
(char *) "Interfacing C APIs and libraries"

>> arr
(char [200]) "Interfacing C APIs and libraries\0\0\0\0\0..."

>> std::cout << "arr = " << arr << "\n";
arr = Interfacing C APIs and libraries
>> 

C memory/buffer management functions - memset, memcpy and etc

Overview

Note: Those functions are defined in the library <string.h> (for C++ <cstring>) for the C programming language.

Buffer/Memory manipulation functions:

  • memset - Fill memory
  • memcpy - Copy memory
  • memmmove - Move data from one location to another.

References

memset

Initializes buffers with some value.

Arguments:

  • s - Pointer to the buffer that will be initialized
  • c - Value that will be used to initialize all bytes of the buffer.
  • n - Number of bytes of the buffer that will be initialized.
void *memset(void *s, int c, size_t n);

Equivalent to:

for(int i = 0; i < n; i++){
    (s + i)* = c;
}

Example:

char buffer[100];
memset(buffer, '\0', 100);

// Initialize an array of 100 integers with 1
int arr[100];
memset(arr, 1, sizeof(int) * 100);

memcpy

Copy bytes from one buffer to another:

Arguments:

  • dst - (destination) - Pointer to destination location where the data will be copied,
  • src - (source) - Pointer to source location where the data will be copied from.
  • n - Number of bytes that will be copied to the destination.
void* memcpy( void* dst,
              const void* src,
              size_t n );

This function is equivalent to:

for(int i = 0; i < n; ++i){
    (dst + i)* = (src + i)*;
}

Example:

char buffer[100];
memcpy(buffer, "hello world!", 12);
printf("message = %s\n", buffer);

Example 2:

when should I use memcpy()?

/* Packet capture buffer */
struct pcbuffer {
     struct pcap_pkthdr *pkt_header;
    u_char *pkt_data;
    int length;    
};
typedef struct pcbuffer packet_capture;
packet_capture packets[PC_BUFFERSIZE];
 
 
struct pcap_pkthdr *pkt_header;              /* I defined it as a pointer */
...
pcap_next_ex(phandle, &pkt_header, &pkt_data);
...
// we have a pointer, but it doesn't point to a valid object, so malloc space for it
packets[in].pkt_header = malloc(sizeof(*(packets[in].pkt_header)));
memcpy(packets[in].pkt_header, pkt_header, sizeof(*(packets[in].pkt_header)));

C++ Wrappers for C libraries or APIs

Example: Nix / getcwd and chdir

getcwd

  • Brief: Get the current working directory.
  • Description: “These functions return a null-terminated string containing an absolute pathname that is the current working directory of the calling process. The pathname is returned as the function result and via the argument buf, if present.”
#include <unistd.h>
char *getcwd(char *buf, size_t size);

chdir

  • Brief: Change the current working directory.
  • Description: On success returns zero and on failure returns -1 and set the flag errno.
#include <unistd.h>
int chdir(const char *path);

Using those APIs in C:

#include <unistd.h>

>> getcwd(NULL, 0)
(char *) "/home/archbox"
>>

>> getcwd(nullptr, 0)
(char *) "/home/archbox"
>> 

>> chdir("/var/log")
(int) 0
>> getcwd(NULL, 0)
(char *) "/var/log"
>> 

Using those functions with C++ string std::string.

#include <iostream>
#include <string>

#include <unistd.h>

>> std::string dirpath = "/etc/";

// Problem: Pass a std::string where const char* is expected 
>> chdir(dirpath)
ROOT_prompt_67:1:1: error: no matching function for call to 'chdir'
chdir(dirpath)
^~~~~
/usr/include/unistd.h:497:12: note: candidate function not viable: no known conversion from 'std::string'

// Solution: 
>> chdir(dirpath.c_str())
(int) 0
>>

>> dirpath.c_str()
(const char *) "/etc/"
>> 

>> getcwd(nullptr, 0)
(char *) "/etc"

>> std::string(getcwd(nullptr, 0))
(std::string) "/etc"
>> 

C++ Wrapper:

auto getCWD() -> std::string {
     return std::string(getcwd(nullptr, 0));
}

auto setCWD(const std::string& path) -> void {
     int status = ::chdir(path.c_str());
     if(status < 0)
         throw std::runtime_error("Failed to change directory.");
}

>> setCWD("/tmp")
>> getCWD()
(std::string) "/tmp"
>> 

>> setCWD("/tmp/dummy")
Error in <TRint::HandleTermInput()>: std::runtime_error caught: Failed to change directory.
>> 

References:

Example: Nix / gethostname (std::string)

Get the computer hostname. This function needs two arguments, an array of chars, which is used for returning the hostname, and the size of the array. If the array size is not enough for returning the hostname, the function returns -1, otherwise it returns 0. The size of hostname “string” is limited to HOST_NAME_MAX.

#include <unistd.h>
int gethostname(char *name, size_t len);

Example: Using this function in C (Root REPL).

#include <iostream>
#include <unistd.h>
char hname[HOST_NAME_MAX];

>> HOST_NAME_MAX
(int) 64
>> 

>> hname
(char [64]) "\0\0\0\0\0\0\0\0\0\0\0\0\0\...."

>> gethostname(hname, HOST_NAME_MAX)
(int) 0
>> 

>> hname
(char [64]) "localhost.localdomain\0\0\0\..."

Example: Using gethostname with std::string

#include <iostream>
#include <unistd.h>

// Create a string with filled with '\0' null character of size HOST_NAME_MAX
>> std::string shname(HOST_NAME_MAX, 0)
(std::string &) "\0\0\0\0\0\0\0\0\0\0\0...."
>> 

>> shname.size()
(unsigned long) 64


>> gethostname(&shname[0], shname.size())
(int) 0

>> shname
(std::string &) "localhost.localdomain\0\0\0\0\0\0\0\0\..."
>> 

>> std::cout << "Hostname = " << shname << "\n";
Hostname = localhost.localdomain
>> 

// Remove trailing \0 characters 
>> std::string x;
>> std::getline(std::stringstream(shname), x, '\0')
>> x
(std::string &) "localhost.localdomain"
>> 

Putting it all together in a C++ wrapper:

 #include <iostream>
 #include <string>
 #include <sstream>
 #include <unistd.h>

 // C++ Wrapper or C++ interface 
 std::string getHostname(){
     std::string shname(HOST_NAME_MAX, 0);
     int status = gethostname(&shname[0], shname.size());
     if(status < 0)
             throw std::runtime_error("Error: failed to retrieve hostname.");
     std::string out;
     std::getline(std::stringstream(shname), out, '\0');	
     return out;
}

>> getHostname()
(std::string) "localhost.localdomain"
>> 

References:

Example: Passing std::vector to a C-array APIs

Many C APIs, such as BLAS and LPACK for linear algebra, provide functions like shown in the following code expecting a C array (pointer to the first address array[0]) and its size. Some functions arguments are used input and other as return value or both. The objective is to build a C++ wrapper using STL-vector for interfacing those APIs.

  • C-APIs with C-arrays.
double average(size_t size, double* const xs ){
     double sum = 0.0;
     for(size_t i = 0; i < size; i++)
             sum += xs[i];
     return sum / size;
}

// xs[i] <- x[i] + a * ys[i]
void arraySum(size_t size, double a, double *xs,  double* const ys){
     for(size_t i = 0; i < size; i++)
         xs[i] = xs[i] + a * ys[i];
}

Testing:

>> double arr [] = {1.0, 2.0, 3.0, 4.0, 5.0};
>> 
>> average(5, arr)
(double) 3.0000000
>> 

>> double xs [] = {1.0, 3.0, 4.0, 5.0};
>> double ys [] = {3.0, 5.0, 6.0, 3.4};
>> 
// =====> Before <<<=========
>> xs
(double [4]) { 1.0000000, 3.0000000, 4.0000000, 5.0000000 }
>> ys
(double [4]) { 3.0000000, 5.0000000, 6.0000000, 3.4000000 }
>> 
 
//======> After <<=========
>> arraySum(4, 2.0, xs, ys)
>> xs
(double [4]) { 7.0000000, 13.000000, 16.000000, 11.800000 }
>> ys
(double [4]) { 3.0000000, 5.0000000, 6.0000000, 3.4000000 }

C++ stl vectors with C-array APIs

std::vector<double> xsv = {1.0, 3.0, 4.0, 5.0};
std::vector<double> ysv = {3.0, 5.0, 6.0, 3.4};
>> 

>> average(xsv.size(), xsv.data())
(double) 3.2500000
>> 

>> arraySum(xsv.size(), 2.0, xsv.data(), ysv.data())
>> 
>> xsv
(std::vector<double> &) { 7.0000000, 13.000000, 16.000000, 11.800000 }
>> ysv
(std::vector<double> &) { 3.0000000, 5.0000000, 6.0000000, 3.4000000 }
>> 

C++ Wrapper:

namespace CPPWrapper{
   auto average(const std::vector<double>& xs) -> double {
       return ::average(xs.size(), const_cast<std::vector<double>&>(xs).data());
   }
   auto linearComb(double a,
                   const std::vector<double>& xs,
                   const std::vector<double>& ys) -> std::vector<double> {
      auto xscopy = xs;
      auto& ysref  = const_cast<std::vector<double>&>(ys); 
      ::arraySum(xs.size(), a, xscopy.data(), ysref.data());
      return xscopy;
   }
};

Testing wrapper:

std::vector<double> xsv = {1.0, 3.0, 4.0, 5.0};
std::vector<double> ysv = {3.0, 5.0, 6.0, 3.4};

>> CPPWrapper::average(xsv)
(double) 3.2500000
>> 

>> CPPWrapper::linearComb(2.0, xsv, ysv)
(std::vector<double>) { 7.0000000, 13.000000, 16.000000, 11.800000 }
>> 

>> CPPWrapper::linearComb(3.0, xsv, ysv)
(std::vector<double>) { 10.000000, 18.000000, 22.000000, 15.200000 }
>> 

// Check whether vectors xsv and ysv were modified. 
>> xsv
(std::vector<double> &) { 1.0000000, 3.0000000, 4.0000000, 5.0000000 }
>> ysv
(std::vector<double> &) { 3.0000000, 5.0000000, 6.0000000, 3.4000000 }
>> 

Example: Posix system-call execvpe (std::string, std::vector)

  • Description: Replaces the current executing process image with an image from a different binary (native executable) or script with shebang at first line #!/path/to/interpreter. If the file parameter.
    • Note 1: If the ‘file’ parameter is not an absolute path, the program is searched in the directories listed in the PATH environment variable.
    • Note 2: This function calls the following entry-point from the invoked executable.
      • int main(int argc, char** argv)
    • Note 3: On Linux, BSD or MacOSX, the documentation of this function be viewed with $ man execvpe.

Signature:

#include <unistd.h>

int execvpe( const char * file, 
             char * const argv[], 
             char * const envp[] );

Parameters:

  • file: Name or path of executable or script which will be launched.
  • argv: Array of null-terminated C-string containing the arguments to be passed to the process. Note: the array must end with NULL pointer and the parameter argv[0] must point to the process name, it cannot be null.
  • envp: Array of null-terminated C-string containing the environment variables to be passed to the process. This array must end with NULL pointer.
  • Return: If the function runs succesfully, it doesn’t return to the caller, otherwise it returns -1 and sets the global variable errno which can set the flags: E2BIG, EACCES, EINVAL, ELOOP …

Example:

  • Failed invocation:
#include <cerrno>  // std::strerror 
#include <unistd.h> // execvep 
int status;

// Parameters to be passed to the process, the first parameter is
// argv[0] or the process name.
const char* args1 [] = {"processName", "-arg1", "arg2", nullptr};    
const char* env1  [] = {"Env1=Value", "KEY=Value2", nullptr};

>> status = execvpe("/do/not/exist", (char *const *) args1, (char *const *) env1);
>> status
(int) -1
>> 

// Sets the global variable errno 
>> errno
(int) 2
>> 

// Get a human-readable error message from error code 
>> std::strerror(2)
(char *) "No such file or directory"
>> 

// Get a human-readable error message from errno variable 
>> std::strerror(errno)
(char *) "No such file or directory"
>> 
  • Wrapping errno into an exception:
#include <system_error>

>> auto ex = std::system_error(errno, std::generic_category());

>> ex.code()
(const std::error_code &) @0x7fe5c614b068

>> std::cout << "code = " << ex.code() << "\n";
code = generic:2

>> ex.code().message()
(std::string) "No such file or directory"

>> throw ex;
Error in <TRint::HandleTermInput()>: std::system_error caught: No such file or directory
  • Successful invocation:
#include <cerrno>  // std::strerror 
#include <unistd.h> // execvep 
int status;

// Parameters to be passed to the process, the first parameter is
// argv[0] or the process name.
const char* args1 [] = {"python-interpreter", nullptr};    
const char* env1  [] = {"Env1=Value", "PROPERTY=Value2", nullptr};

status = execvpe("python3", (char *const *) args1, (char *const *) env1);

// The current process is killed and it is replaced by the python3 process 
// 
>> status = execvpe("python3", (char *const *) args1, (char *const *) env1);
Python 3.6.6 (default, Jul 19 2018, 14:25:17) 
[GCC 8.1.1 20180712 (Red Hat 8.1.1-5)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> print("Loaded Python Interpreter OK!")
Loaded Python Interpreter OK!
>>> 
>>> 

>>> os.environ["Env1"]
'Value'

>>> os.environ["PROPERTY"]
'Value2'
>>> 
  • In another terminal is possible to confirm the process name:
$ ps -ef | grep interpreter
archbox  10978 10977  0 14:47 pts/2    00:00:00 python-interpreter

$ kill 10978

C++ Wrapper:

// ----- C++ standard library headers ------// 
#include <vector>
#include <string>
#include <cerrno>
#include <system_error>
#include <algorithm>   // std::transform 
#include <functional>  // std::bind 

//---- Nix-sepecific headers -------------//
#include <unistd.h> // execvep

namespace NixUtils{
    void execProcess(
            const std::string& program,
            const std::vector<std::string>& args = {},
            const std::vector<std::string>& env  = {})
    {
       // Import placeholders, _1, _2, _3 ... 
       using namespace std::placeholders; 
       using std::vector; using std::string; using std::transform;
       vector<const char*> pargs(args.size());
       vector<const char*> penv(env.size());
       transform(args.begin(), args.end(), pargs.begin(), std::bind(&string::data, _1));
       pargs.push_back(nullptr);
       transform(env.begin(), env.end(), penv.begin(), std::bind(&string::data, _1));
       penv.push_back(nullptr);
       int status = ::execvpe(program.c_str(),
                              (char* const*) pargs.data(),
                              (char* const*) penv.data());
       std::cerr << " [TRACE ]status = " << status << " errno = " << errno << "\n";
       if(status < 0)
          throw std::system_error(errno, std::generic_category());
    }
}

Usage example:

>> NixUtils::execProcess("/does/not/exist")
 [TRACE ]status = -1 errno = 2
Error in <TRint::HandleTermInput()>: std::system_error caught: No such file or directory

// Exit current process 																	  
>> NixUtils::execProcess("python3", {}, {"env1=Value", "num-threads=10"})
Python 3.6.6 (default, Jul 19 2018, 14:25:17) 
[GCC 8.1.1 20180712 (Red Hat 8.1.1-5)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

>>> print("Inside python3 interpreter loaded from C++")
Inside python3 interpreter loaded from C++
>>> 

>>> import os
>>> os.environ["env1"]
'Value'
>>> os.environ["num-threads"]
'10'
>>> 

References

C++ headers and functions used:

Documentation of execvpe system-call:

Misc:

Example: GSL / C++ Class Wrapper to an C-API in OOP style or with opaque pointers.

This example shows how to build a C++ wrapper for C-APIs written in object-oriented C style. It means, C-APIs which emulates OOP concepts by using functions which encode class methods and a pointer pointer for emulating an object. In this style, the pointer to the data representation is passed around the functions emulating class methods.

This style can also use opaque pointers for full encapsulation and data hiding. Opaque pointers to incomplete types where the data representation struct is not exposed to the client code. Many old C-libraries uses this style, including Windows Win32 API.

In example case, it will be used the vector “class” from GNU Scientific library, but this approach can be used in other similar scenarios.

See more at:

GSL C-Vector API

Basic Functions:

// Constructor: new gsl_vector(10)
// Allocate vector -> Similar to a constructor 
gsl_vector* gsl_vector_alloc (size_t n)

// Destructor: ~gsl_vector(){}
// Deallocate vector -> Similar to a destructor 
void        gsl_vector_free (gsl_vector* v);

// Accessor method => double x = vector.get(index)
// Get vector element 
double      gsl_vector_get (const gsl_vector * v, const size_t i)

// Mutator mehtod => vector.set(10, 2.34);
// Set vector element
void        gsl_vector_set (gsl_vector* v, const size_t i, double x)

// Mutator method => vector.setall(1.25)
// This function sets all the elements of the vector v to the value x.
void        gsl_vector_set_all (gsl_vector* v, double x)
// This function sets all the elements of the vector v to zero.
void        gsl_vector_set_zero (gsl_vector* v)

Vector Operations:

// a <- a + b 
int gsl_vector_add (gsl_vector * a, const gsl_vector * b)
// a <- a - b
int gsl_vector_sub (gsl_vector * a, const gsl_vector * b)
// a <- a * b
int gsl_vector_mul (gsl_vector * a, const gsl_vector * b)
// a[i] <- a[i] * SCALE
int gsl_vector_scale (gsl_vector * a, const double x)
// a[i] <- a[i] + CONSTANT 
int gsl_vector_add_constant (gsl_vector * a, const double x)

C++ Wrapper

  • Class GSLVector - encapsulates the GNU Scientific library vector “object” which is written in object-oriented C style. In this style, methods are encoded as C-functions and the object is encoded as pointer which is passed as function argument. Despite the style this GLS API is written, it still not convenient for using in C++. This example shows how to encapsulate an API like this in a C++ class.

Complete file: src/gsl-vector-wrapper.cpp

class GSLVector{
private:
    gsl_vector* m_hnd;
    int m_size;
public:
    GSLVector(int m_size) {
        this->m_size = m_size;
        this->m_hnd = gsl_vector_alloc(m_size);
    }
    // Overloaded constructor
    GSLVector(int m_size, double x) {        
        this->m_size = m_size;
        this->m_hnd = gsl_vector_alloc(m_size);
        gsl_vector_set_all(m_hnd, x);
    }
    // Copy constructor 
    GSLVector(const GSLVector& src){
        m_size = src.m_size;
        m_hnd = gsl_vector_alloc(src.m_size);
        gsl_vector_memcpy(m_hnd, src.m_hnd);
    }
        // Copy assignment operator
    GSLVector& operator= (const GSLVector& src){
        std::cerr << " [TRACE] Copy assignment operator." << "\n";
        if(m_size != src.m_size){
           gsl_vector_free(m_hnd);
           m_hnd = gsl_vector_alloc(src.m_size);
         }        
        gsl_vector_memcpy(m_hnd, src.m_hnd);
        return *this;
    }	
    // Destructor 
    ~GSLVector(){
        if(m_hnd != nullptr)
           gsl_vector_free(m_hnd);
    }
    size_t size() const { return m_size; }

    gsl_vector* data(){
        return this->m_hnd;
    }
    struct ElementProxy{
         GSLVector* ptr;
         size_t     index;
         ElementProxy(GSLVector* ptr, size_t index): ptr(ptr), index(index){}
         ElementProxy& operator=(double x){
             gsl_vector_set(ptr->data(), index, x);
             return *this;
         }
         double get() const {
              return gsl_vector_get(ptr->data(), index);
         }
    };

    ElementProxy operator[](size_t index){
       return ElementProxy(this, index);
       // return gsl_vector_get(m_hnd, index);
    }
    // double operator[](int index){
    //     return gsl_vector_get(m_hnd, index);
    // }	
    void set(int index, double x){
        gsl_vector_set(m_hnd, index, x);
    }
    double max() const {
        return gsl_vector_max(m_hnd);        
    }
    double min() const {
        return gsl_vector_min(m_hnd);
    }
    GSLVector operator + (const GSLVector& va){
        // Invoke copy constructor 
        GSLVector vec2 = *this;
        gsl_vector_add(vec2.m_hnd, va.m_hnd);
        return vec2;
    }
    GSLVector operator * (double scale){
        // Invoke copy constructor 
        GSLVector vec2 = *this;
        gsl_vector_scale(vec2.m_hnd, scale);
        return vec2;
    }
    friend std::ostream& operator<<(std::ostream& os, const GSLVector& vec){
        os << "[" << vec.m_size << "](";
        for(int i = 0; i < vec.m_size; i++){
            os << std::setprecision(4) << std::fixed << " " << gsl_vector_get(vec.m_hnd, i);
        }
        os << ") ";
        return os;
    }
};

Loading in CERN’s ROOT REPL:

>> .L gsl-vector-wrapper.cpp 
>> GSLVector v(5);
>> 
>> v.size()
(unsigned long) 5
>> 
>> std::cout << "v = " << v << "\n";
v = [5]( 0.0000 0.0000 0.0000 0.0000 0.0000) 
>> 

>> v.set(0, 3.0); v.set(1, 4.5); v.set(2, 3.25);
>> std::cout << "v = " << v << "\n";
v = [5]( 3.0000 4.5000 3.2500 0.0000 0.0000) 
>> 

>> std::cout << "v * 2.0 = " << v * 2.0 << "\n";
v * 2.0 = [5]( 6.0000 9.0000 6.5000 0.0000 0.0000) 
>> 

>> auto a =  v * 2.0;
>> std::cout << "a = " << a << "\n";
a = [5]( 6.0000 9.0000 6.5000 0.0000 0.0000) 
>> 
>> std::cout << "v = " << v << "\n";
v = [5]( 3.0000 4.5000 3.2500 0.0000 0.0000) 
>> 

>> v.max()
(double) 4.5000000
>> v.min()
(double) 2.3715151e-322
>>   

Main function:

GSLVector vec1(5, 2.45);
vec1.set(0, -3.45);

std::cout << std::boolalpha;
std::cout << "vec1 = " << vec1 << "\n";
// test copy constructor
std::puts("Create vec2 - before invoke copy constructor");
GSLVector vec2 = vec1;
std::cout << "vec1 = " << vec2 << "\n";
std::cout << "&vec2 == &vec1: " << (&vec1 == &vec2) << "\n";
assert(&vec2 != &vec1);
vec2.set(0, 10.0);
vec2.set(1, 25.0);
vec2[3] = 2.30;
std::cout << "vec2 = " << vec2 << "\n";

GSLVector vec3 = vec1 + vec2;
std::cout << "vec3 = " << vec3 << "\n";
std::cout << "vec1 * 3 + vec2 * 2.5 = " << vec1 * 1.5 + vec2 * 2.5 << "\n";

Compilation Output:

$ clang++ gsl-vector-wrapper.cpp -std=c++11 -Wall -Wextra -g -lgsl -lgslcblas -o out.bin 
$ ./out.bin

vec1 = [5]( -3.4500 2.4500 2.4500 2.4500 2.4500) 
Create vec2 - before invoke copy constructor
vec1 = [5]( -3.4500 2.4500 2.4500 2.4500 2.4500) 
&vec2 == &vec1: false
vec2 = [5]( 10.0000 25.0000 2.4500 2.3000 2.4500) 
vec3 = [5]( 6.5500 27.4500 4.9000 4.7500 4.9000) 
vec1 * 3 + vec2 * 2.5 = [5]( 19.8250 66.1750 9.8000 9.4250 9.8000) 

References: