Published on

Pointer, Array, and References

Overview

This blog post deals with C++ construct for referring memory. Obivously one can easily access the corresponding object with its equivalent name or the copy of such object, however, accessing the direct copy of the each of the object for every corresponding operation such as while passing it as a function argument, accessing it within a corresponding sequences and pertaining to particular index of that sequence can be relatively expesive. Especailly when the object is of a type that is argubly too large or expensive to copy such as a string descriptor or a complicate user-defined type declration defining a data structure to hold onto the representation of sequences of arbitary types, etc.

However, almost most of the entity or objects in C++ resides at a particular memory address and hence can be accessed as such by the given memory address corresponding to the memory location where the object of a particular type is being allocated. The language construct of refferring to these addresses in C++ are called pointers and references.

Pointers

For an object of type T, T* notion is generally used to represent a pointer to the type T. In crude sense one can comprehend pointers as an integral descriptor(in particular) hexadecimal descriptor pointing to the memory address where the given object of type T is being stored. And since each of the object of the given type might be allocated at a different word bit boundary, we can mix pointers to different types as discussed earlier in the previous blog. Usually one of the obvious operation the given pointer supports is the derefercing operation i.e. getting the reffernce to the value being pointed to by the given pointer so as the perform the inteded operation on such values. The derefercing operator is a prefix uniry operator often know as asterisk or *. Thus, a pointer in a crude sense provide us an indirection mechanism to access an object of type T with its memory address as pointed to by an integral descriptor corresponding to the given pointer. Consequently one can avoid using a relatively expensive copy operation as and when needed and can get the access to the relavant object with the said mentioned indirection. For example:


#include<iostream>
using namespace std;
template<class T>
void use_Pointers(){
  T a = T{};
  T* ptr = &a; //one can get the address to the given object with the addressof operator &.
  cout << ptr << endl;
  if(typeid(T{}).name() == typeid(int{}).name() || typeid(T{}).name() == typeid(float{}).name()){
    *ptr = T{} + 12;
  }else{
    throw std::runtime_error{"cant have this"};
  }
  cout << *ptr << endl;
}
int main(){
  use_Pointers<int>();
  return 0;
}

In the above mentioned example we are reassigning or changing the corresponding value of the object being pointed to if it happens to be an integral literal or a floating point literal to assume the value of 12 in the given type.

The implementation to the pointers and references are meant to map to the addressing mechanisms on the machine in a rather implementation independent manner, mostly owning to the fact that even though each of types may have different implementation specific properties such as the size, range, precision amongst different type may differ across various systems implementing C++, we can still store each of the corresponding types in memory address references by descriptor we called as pointer to the given type.

The * meaning pointer to is used to prefix the type name for a given pointer to that type. However, pointer to function and pointer to array have more complicated notion that usually relies on the fact that the postfix operator binds much more tighter than the prefix operation. The said notions would be discussed later as and when needed.

Void*

In low-level code, we occasionally needs to pass along or store the address of the memory locaation to the given object, without actually knowing the type of the object being stored there. A void* pointer supports such notions. Consequently, a void* is often used to represent pointer to unknown type and is often used as a return type to a function returning an object about whose type we aren't allowed to make any assumptions. As such a pointer to any object can be assigned to void* however, we cant have a pointer to function and pointer to member being assigned to void*. Moreover, a void* pointer can be assigned to another void* pointer and check for equality, etc., however other operations that would otherwise, require us to know the space allocated to the given object, and hence it type such as dereferncing such pointer are usually undefined without explicit conversion to the inteded type, cause there is no way to determine the type for a object pointed to by a void* pointer at compile time. Moreover, when using void* with explicit conversion to access the object being pointed to one should keep in consideration that the type being casted is exactly the same as the original type of the object being pointed to, cause object of different type might be allocated at different word bit boundary, and hence using a pointer to a different type used to store an object that wasn't the inteded type could possibly result in undefined behavior. Generally speaking void* pointers are meant to be used in a relatively low-level code where real hardware resources are manipulated for instance while providing the raw untyped memory to be allocated for the object for given type, etc. Thus, when frequently used in a higher level code, one should stall a little and rethink their use as such use are generally a likely indicator of design flaws or potentially errors. More so, if the use of such pointers are necessary one should try and encapsulate the code relying on the direct use of raw void* pointer in a better well-defined User-defined type or resource handler managing the corresponding resource(here memory being allocated).


#include<iostream>
using namespace std;
void* allocate_mem(size_t n){
  void* raw_mem = malloc(n);
  return raw_mem;
}
void use_void_ptr(){
  int ptr = 12;
  int* ptr1 = &ptr;
  void* r = allocate_mem(sizeof(int));
  void* q = r; //OK assignment to a pointer to given type.
  r = ptr1;
  cout << (r == q) << endl; //OK checking equality.
  #ifdef errors
  cout << *r << endl; //error cant dereference a void* pointer without explicit type conversion
  #endif
  cout << *reinterpret_cast<int*>(r) << endl; //OK type_Cast into the inteded type.
  cout << *reinterpret_cast<float*>(r) << endl; //error type_cast into unrelated type.
}
int main(){
  use_void_ptr();
  return 0;
}

Nullptr

The literal nullptr represent null pointer, i.e. a pointer pointing to no object. In general sense, it's usually good to have a default value for a given type much in the same sense nullptr pointer a default value for the given pointer if not otherwise, intialized in the scopes excluding the dynaically created object, and local scope, etc. Since nullptr doesn't actually points to any object, indirection on nullptr is usually not allowed and consequently results in errors or cathestropic results. As such to preserve the generality of the language there is just a single representation of nullptr as opposed to having different nullptrs for different types. Consequently much like pointers a nullptr can be assigned to pointers of given type but not to any object of given type.

Before nullptr was introduced, zero was often used to represent all the zero-bit pattern address specifying the addressing allocated to no object, and hence the nullptr. However, Since the integral value zero can be used both as integer and as a pointer in such scenarios as provided by standard implementation, it can often lead to quirky problem mostly involving overloaded instances of functions that are overloaded to accept either an integer or pointer, for which case using 0 integral literals to represent a pointer could lead to potential ambiguation in overladed instance such as somefunction(0), etc. This was a source of subtle error and an eyesore of usage of integral value as a constant for pointer to objects, or members at that. Consequently, such use of nullptr are often strongly discouraged in the mordern context of C++.

However, in addition to zero, it is also popular to define a Macro NULL to represent the nullptrs. For example one can easily represent a nullptr with such notion as follows:

int* ptr =NULL;

But since NULL could have different meaning in different context, for instance while in C it is usually interpreted as 0L or 0LL, the loose inteprentation of NULL macro in CPP usually leans to an equivalent of void*(0), which often makes it illegal to use in CPP.

Consequently, as far as Cpp is concerned using nullptr to represent null pointers is not only retortically the safest of the lot of the above-mentioned alternative, but often provide an easier interpretation model on the inteded meaning the language construct for null pointers.


#include<iostream>
#include<cstring>
#define choice Kind::NULLPTRS;
using namespace std;
enum class Kind{DEFAULT_STR, NULLPTRS};
void nullptrs(Kind k1){
  int* ptr = 0;
  int* ptr1 = nullptr;
  int* ptr2 = NULL;
  const char* ptr3;
  const char* default_string = "default_string";
  cout << (ptr == ptr1 && ptr1 == ptr2) << endl;
  switch(k1){
    case Kind::NULLPTRS : {
      ptr3 = nullptr;
      break;
    }
    case Kind::DEFAULT_STR : {
      ptr3 = default_string;
      break;
    }
  }
  if(ptr3 && strlen(ptr3)){
    cout << ptr3 << endl;
  }else{
    cout << "cant derefernce nullptr " << endl;
  }
}
int main(){
  Kind k1 = choice;
  nullptrs(k1);
}

Arrays

For Type T, T[size], often defined an array for type T and const-bound size, size. Arrays are quite a primite and fundamental way of CPP to represnet fixed length sequence of objects of same types in contiguous block of memory. Consequently, they are best suited for such uses only but for anything else such as a dynaically growing sequences, we should better use a more well-defined data strcture such as a std::array, vector, valarray, etc., or should encapsulate the given array in the given user-defined type with inteded operations defined on it. In general array usually suffer from the following:

  • Since arrays are rather primitive type, the way they're implemented leads them to implicitly converts to the pointer to the first element on the slighest provocation. Consequently, one can't pass an array by value to a function. Moreover, this very behavior exhibited by array usually still remains the source of subtle errors in most of the C or old-styled C++ codes.
  • Since the primitive array implementation doesn't have any advanced user-defined semantics such as move, or copy operations defined, it is rather treated as a plain-odd data, meaning it can't be assigned to another array even if it happens to have same no of elements of the same type. Consequently, if you want to assign a sequence of object to another such sequence you should better use a well-defined sequence such as a vector.
  • The conversion of array name into the pointer to the first element of such sequence essentially means there is no way to determine the size of such seqeuences without explicitly declaring or house-keepoing it.
  • An array can only have a fixed length, therefore it can be specify its size as const bound constexpr or const integral value.

As such, an array can be allocated either on the stack or the free-store. If you define an array on the free-store you are usually responsible for manging the resource(memory) used by such array. This is generally done by calling delete[] operator after the last intended use of such allocated array in the given translation unit or scope, specifically to release any resources such as memory acquired by such object. Such explicit mangement of memory however, is usually hard in large programs mangining a lot of objects allocated on free-store, consequently such operations should be left best with a resource handler such as a unique_ptr or shared_ptr that implicitly does the resource mangement during the construction and descructions of the object, without having to bog the user to keep track of each such allocated object and effectively deleting them as and when required.

Though each of the fore-mentioned advice may sound prudent in context of C++, however the same can't be said to the equal intent with C or old style C++ code, mostly because C lack the ability to encapsulate. It doesn't however, render the mentioned advice irrelavant in context of C++.


#include<iostream>
using namespace std;

void accessing_out_of_bounds(int arr[2]){
  //dont pass array by value as range checking at runtime is often not garunteed. Moreover, array implicitly converts to pointer to its first element.
  int* ptr =arr; //OK converts to pointer to first element i.e. int.
  for(size_t i = 0; i < 12; i++){cout << arr[i] << endl; arr[i] = 13;} //error acessing out of range elements.
  for(size_t i = 0; i < 12; i++){cout << ptr[i] << endl; ptr[i] = 12; }//wrong but possibly more sane.
}
void use_arrays(){
  constexpr int const_bound_size = 12;
  int non_const_bound = 2;
  int arr[2]{1, 2};
  cout << arr << endl; //converts to poitner to first element.
  int arr1[const_bound_size]{1, 2, 3}; //OK provided const-bound size.
  #ifdef errors
  int arr2[non_const_bound]{1, 2}; //error cant have non-const bound size.
  accessing_out_of_bounds(arr);
  int arr1[2] = arr; //error no copy constructor defined on built-in array type.
  delete[] arr; //error cant use delete operator on statically allocated object whose lifetime is controlled automatically within the given scope.
  #endif
  int* newarray = new int[1024]{}; //dynamically allocated array;
  for(size_t i = 0; i < 1024; i++){cout << newarray[i] << " "; }
  delete[] newarray;
}
int main(){
  use_arrays();
  return 0;
}

Array intialization

An array can be intialized either with direct intialization to the list of value or list based intialization to the given homogenous values of the given type. However, even though the direct intialization using = seems redudant it is mostly used in older context to indicates that a list of values have been intialized to the given sequence. In accordance to the following sytax one can run into the following scenarios:

  • When not explicitly specying the bound, the bound of an array is determined to be the number of the elements specified in the intializer. This notion is often exploited interchangibly with the pointers to first element when passing array value as function arguments.
  • When specifying the bound but intilizing with fewer element than specified, the rest of the elements will be intialized with the default value of the given type of the array.
  • When explicitly specifying the array, providing sulprus elements than the specified bound could result in complitation errors.
#include<iostream>
using namespace std;
void use_array(int arr[]){
  int* ptr = arr;
  cout << (ptr == &arr[0] ? "True" : "False") << endl;
  int arr1[]{1, 2}; //OK size implicitly determined.
  #ifdef errors
  int arr[3]{1, 2, 3, 4};
  #endif
  int arr2[1024]{1, 2, 3};
  int sum = 0;
  for(size_t i = 0; i < 1024; i++){
    if(arr2[i] == 0){sum += 1;}
  }
  cout << (sum == 1021 ? "True" : "False") << endl;
}
int main(){
  int arr[3]{1, -2, 2};
  use_array(arr);
  return 0;
}

String Litearls

In C++ the C-style String litearls are often stored as a sequence of const char or so to speak array of const char. Consequent Since, the array name implicity converts to the pointer to the first element on the slighest provication, C-Style string ltierals can often be referenced with a accessed at a relatively cheaper cost than accessing the copy of the same with pointer to const character. As such a string literal generally consist of one more character than then it original length of such sequences that corresponds to the null character often represented by \0. Such notion of null character is usually exploited to determine the length of such seqeunces since the array size is generally lost during the conversion to the pointer to the first element. Consequently, while dealing with the string, unlike most of the array to the given type, one usually don't need to explicitly keep track of size of such litearls.

In Earlier instance of C++ implementation or C code however, one can usually store string literals as pointer to non-const char. That however, as obivous it may appear was the source of subtle errors and confusions as storing string litearls as a const char is not only obvious but allows for significant optimizations in the way the corresponding literal can be stored and accessed. If however, you want a string that we're garunteed to modify you can still you a non-const char array. However assing a string literal with a pointe to non-const char will obviously result in compilation error as a char* is generally used to specify a pointer to a single char literal only as opposed to sequences of character. A string literals is staically allocated, consequently it can safely be return from a function. More to speak the memory holding such string literal will not go away while returning it from a function.

Whether two identical string literals are allocated at the same memory address however, is implementation-defined however, while comparing such string literals we usually meant comparing the address(pointer value) as opposed to the actual value of such string litearls. Moreover, as discussed in the previous section discussing the character literals, we can actually store the non-graphic character in string literals that don't have any convinient representation on the keyboard as an escape sequences as discussed in the preceeding sections here. More so often one tend to store a large string literal for which case it could combersome to represent the entire sequence within a single line, however, one can't break the narrow or ordinary string literal as such cause C++ parser usually consider newlines as the end of the statement consequent a statement ending without a semicolon generally renders as an expression and an unterminated expression can lead to compilation error. However, one can easily concatenate the long string literals broken by whitespaces by write them as individual literals as opposed to a single literal.

It is possible to have more than one null character within the given string literal however, most of the program will suspect there is nothing following the null character. Consequently, anything following a null character within a C-style string literal is often ignored by the compiler while parsing such sequence.


#include<iostream>
#include<cstring>
using namespace std;
const char* not_statically_allocated(){
  string message = "somemessage";
  return message.c_str(); //might actuall points to object that has already being destroyed in the given scope
}
const char* statically_allocated_string_literals(){
  return "somestring";
}
int strcmp1(const char* str1, const char* str2){
  int size1 = strlen(str1);
  int size2 = strlen(str2);
  if(size1 < size2){
    return -1;
  }else if(size1 > size2){
    return 1;
  }else{
    bool res{true};
    for(size_t i = 0; i < strlen(str1); i++){
      if(str1[i] != str2[i]){res = false;}
    }
    if(!res){
      return str1[0] - str2[0] > 0 ? 1 : -1;
    }else{
      return 0;
    }
  }
}
int main(){
  const char randomstring[]{"something"};
  cout << (sizeof(randomstring) - 1  == strlen(randomstring) ? "True" : "False") << endl;
  const char* r = randomstring;
  cout << (int)r[strlen(r)+1] << endl;

  char nonconstchar[]{"zero"};
  nonconstchar[0] = 'R';
  cout << nonconstchar << endl;
  try{
    const char* names = not_statically_allocated(); //undefined might be accessing unrelated memory location threfore reading and writing to such memory location can lead to runtime errors or exceptions being throw.
    cout << names << endl;
    const char* s = statically_allocated_string_literals();
    cout << s << endl;
  }catch(...){
    cout << "something went wrong" << endl;
    exit(1);
  }
  const char* samestring = "samestring";
  const char* samestring1 = "samestring";
  #ifdef CPP_PREV_BEHAVIOR
  char* somestring = "sometstring";
  somestring[2] = 'R';
  #endif
cout << samestring << " " << samestring1 << " " << (samestring == samestring1 ? "True" : "False") << endl;
  const char* embedEscapeSeqeunces = "a\bc\n\xfg";
  for(const char* start = embedEscapeSeqeunces; *start != '\0'; start++){
    cout << (int)*start << " ";
  }
#ifdef errors
  const char* narrow_String_literals = "abcd
efgh";
#endif
  const char alpha[] = "abcdefghijklmnopqrstuvwxyz"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  cout << alpha << endl;
  const char* terminated_string = "Jam\0es\00Marks";
  cout << (strcmp1(terminated_string, "Jam") == 0 ? "True" : "False") << endl;
  return 0;
}

Raw String literals

It is sometime prudent to just treat the string literal in its raw form that without considering any special parsing rules for escape seqeunces such as a \ or ". These instance become much prelavant when dealing with regular expression where the next character in the sequence can not only be used to represent an escape character but can also be used to introduce a new character class. For such cases C++ provides the notion of raw string literals which treats newline as just newline and any whitespace as just as whitespace. For instance consider the following example:

const char* rchar = '\\w\\\\ww';

Such criptic representation and hence the meaning of characters can be hard to deduced even for a seasoned programmer. Conseqeuently, a raw string literal is often preferrable in such instances especially to make the representation of string literal rather intuitive as opposed to be hard to deduce. Consequently, the above example can equivalent be written in a raw string literal as follows:

const char* rawstringliteral = R"(\w\\ww)";

A Raw string literal use the notion R"(cc)" where the prefix R is used to distinguish narrow string literals from raw string literals, and CC represent the requisite character sequences which in turn are delimited with a pair of braces that allows for unescaped double quotes. But at the closer look at the given syntax one might argue how we can represent the character sequence ")" for instance. Fortunately, that's a rare problem but "(" and ")" is only the default delimited pair, therefore, one can introuduce delimiter before "(" and after ")". However, the character sequence before "(" must be exactly the same as the character sequence after ")". That way one can cope up with any arbitary sequences or pattern. For example consider the following block of code:

const char* escaped_parathenses = R"***("quoted sequence terminated by (")")***");
cout << escaped_parathenses << endl;

The following example would successfully delimate the ( and ) while using the arbitary sequences of character literal before and after the delimited pair of sequences.

Unless you work with regular expressions however, raw string literals should probably be just treated as a curosity and one more thing to learn. However, since regular expression are quite povital and widely used in wide variety of scenario one should still consider skimming through raw string literals either way. For instance consider a real world example:

const char* res = "('(?:[ˆ\\\\']|\\\\.)∗'|\"(?:[ˆ\\\\\"]|\\\\.)∗\")|";

with example like this, even expert programmer can become a little bit dubious of their interpretation of the above string especially when used in absence of raw string literal. Moreover, in contrast to narrow strings, raw string literals can also contain new lines, for instance example such as the following:

string res{R("12
22
333")};

would probably require a more tedious and error prone representation such as the following with narrow string literals:

stirng res{"12\n22\n33"}

Larger Character Set

Much like we can represent character literals in a much richer character set tham ASCII, at least in terms of range supported by the given literals, we can also represent string litearls in a much richer character set, which are often preffixed with L, representing the string literal with base type wchar_t[] where wchar_t represent wide character, i.e. character set capable enough of holding largest characters as supported by the given implementation locale. These sort of larger character set used to define a string are often called as unicode character literals. As such there are six character literals supporting unicode. These might sound a little bit excessive at first, there are three major encoding of unicode i.e. UTF-8, UTF_32, and UTF-32, and each of such encodings can be used to represent either normal or raw string literals. Moreover, each of the unicode encoding support all of the unicode literals, however, which one you might lean to use might depends on the system you need to fit into. For instance UTF-8 encoding is usually interpreted as variable length character encoding meaning it can encompass a wide variety of character literals while keeping the result document size minimal so as to fit common characters in a byte, frequently ocurring one in 2 bytes and rarer in 3 or 4 bytes. More so, a unicode literal can be used to represent even ASCII characterset in almost equivalent amount of memory as the original ASCII characterset.

A UTF-8 string literal is often terminated by a \0, a UTF-16 by u'\0', a UTF-32 by U'\0', etc. Consequently, though each of the characterset might be used to represent similar string literals, but the way they might store string literals might differ significantlly from one another. For instance one can store the string representing the filepath in each of the unicode encoding as well pertaining to both normal and raw string literal as follows


#include<iostream>
#include<cassert>
using namespace std;
template<class T>
void traverseUTF(T* string_literals){
  while(true){
    if(*string_literals){
      cout << (char)*string_literals++;
    }else{
      break;
    }
  }
  cout << endl;
}
void larger_character_set_string_literals(){
  const char* utf8literal = u8"file\\newfile\\folder";
  const char* rawutf8literal = u8R"(file\newfile\folder)";
  const char16_t* utf16literal = u"file\\newfile\\folder";
  const char16_t* rawutf16literal = uR"(file\newfile\folder)";
  const char32_t* utf32literal = U"file\\newfile\\folder";
  const char32_t* rawutf32literal = UR"(file\newfile\folder)";
  traverseUTF<const char>(utf8literal); traverseUTF<const char>(rawutf8literal);
  traverseUTF<const char16_t>(utf16literal); traverseUTF<const char16_t>(rawutf16literal);
  traverseUTF<const char32_t>(utf32literal); traverseUTF<const char32_t>(rawutf32literal);
  traverseUTF<const char>(u8"The Symbol for sigma is \u03A3");
}
int main(){
  larger_character_set_string_literals();
  return 0;
}

Even though each of the printed string literal might seems exactly the same, except for the plain and UTF8 string literal their internal representations are likely to differ. However, one of the inteded purpose of unicode string literal is of course to facilitate embedding unicode literas into them, for instance printing u8"The unicode for sigma is \u803A3"; will give you the output "The unicode for sigma is Σ" The hexadecimal number after the \u is called the unicode code point. Such a code point is generally independent of the encoding used and will In fact, have different value in different representations of the string literals.

Pointer into Array

Since the Array implicitly converts to the pointer to the first element, there is a relatively close relationship between pointer and arrays, and in fact we can access each of the corresponding elements of array with the given pointer to the first element, and performing the subsequent pointer arthematic operations i.e. either pointing to next element in the sequence by adding a scalar value to the poitner or incrementing it with the post or preincrement operator or subtracting a scalar value to access the previous element with the same effect. As such while access the array element we should make sure that we're only trying to access the array element within the well specified bound. As accessing the array element one past first element and one beyound one past the last element could usually lead to accessing unrrelated element thereby any write or read operation at the consequent memory address as being pointed to by the given pointer could lead to undefined behavior and usually cathostrophic, In fact, while using a compiler with significant optimizations such access can often trigger hardware exception or error. Moreover, addition and any other arthematic operations on them other than addition, and substraction of the scalar doesn't really makes any sense while traversing a sequence of object of same type in a well-defined range doesn't really make sense and hence is often not allowed. Moreover, as discussed above we cant pass an array by value to a function, and the implicit conversion of array into the pointer to the first element means the size of the array also get lost in the conversion. Consequently, if you want to access the size for the particular array to perform operation on such array such as traversal, sorting, addtion of elements, etc., you need to either explicitly specify the size or use a better-well defined data structure with much level of abstraction on corresponding advanced user-defined semantic such as copy or move operations as well the way to derrive the size defined on it. Some of such data structure might include std libraray container types such as vector, valarray or std::array to name a few. However, despite the relative low leve construct for an array, we must take into consideration that most of the Standard libraray function maintaing compatibity with C-Style code generally deals with array and are often defined to take pointer to the first element of the array as an argument. For instance the strlen(), strcmp() method defined on string factory class generally take pointer to the first element of the corresponding string as an argument and relies on the null character to effective identify the end of the string literal, meaning it only consider the no of the element upto but not including the null character while traversing the string literals. But even though might sound a little tarny, that still pretty low level and most of the standard libraray container doesn't suffer from this problem. They provide the way of determining the no. of elements of the container with the corresponding size() method without having to count them each time the user need to traverse such sequence.

Traversing Array

One of the key for writing any effecient algorithm is accessing the array element in the well specified range, as discussed in the above section we should access the array elements only within the bounds as specified by the array. As such the traversal of an array can be done either through the pointer to the first element and the correspond index or via the subscripting operation. But in either of the said cases, we need to determine the size of the array. This could be done either by explicity metioning the given size or encapsulating the array in a well-defined data structure specying the method to determine the size for that array. For instance the below-mentioned code segmented show how to access array element in a 1D array either through index or pointer to the element plus index.

void oneDTraversal(int arr[], size_t n){
  for(size_t i = 0; i < n; i++){cout << arr[i] << " ";}
  for(int* r = arr; r != &arr[n-1]; i++){
    cout << arr[i] << " ";
  }
}

Usually while accessing the array element with pointer + index, the corresponding operation will result in the pointer pointing to the next element in the sequence if used with increment operator or previous element in sequence if used with the decrement operator such. Therefore the resulting pointing would be pointer + sizeof(T), away from the previous pointer, where T corresponds to the type of object being stored in the sequence. Subtracting two pointer in sequence however, will give you the no of element between thaose pointer in the sequence. For instance for pointer p and q the no of element between p and q but not including q can be specified as p-q = [p, q). Consequently, we can use the given code segment to identify the size of the given type which generally corresponds to an implementation defined property differing across different implementation or machine on which the CPP program is to run.

The array as a data structure to hold onto to the representation of seqeunce of element of same type in fixed amount of memory is relatively low-level and most of the adventage of using such array along with the few disadventages can be obtained by using standard library cotainer such as array, vector, valarray, etc. Moreover, despite of the fact that some C++ implementation might offer you optional range checking such range checking is often expensive and hence usually employed as a development aid rather than production aid. Consequently, if you are not using optional range checking to access each of the individual array element you should ensure that you ended up accessing array element only in a well-defined range. That is best done by encapsulating array in a higher-level container type such as vector which is much harder to get confused or wrong about the range of valid elements.

#include<iostream>
using namespace std;
struct Person{
  const char* address; //8 bytes.
  char no; //4 + 1 + 3(Structral padding) = 12
  int id; //16
};
template<class T, size_t N>
void byte_diff(T* start, T* end){
  cout << start << " " << end << endl; //array convert to the pointer to its first element the element
  cout << ((end - start) == N ? "True" : "False") << endl;
  int diff = (reinterpret_cast<char*>(end) - reinterpret_cast<char*>(start)) / N;
  cout << diff << endl;
}
void array_indexing_and_pointer_equivalence(){
  const int n = 5;
  int arr[n]{1, 2, 3, 4, -2};
  cout << ((*(arr + 1) == arr[1]) && (*(1 + arr) == arr[1]) && (1[arr] == arr[1]) && (1[arr] == *(arr + 1))) << endl;
}
int main(){
  const int N = 5;
  int arr[N]{1, 2, 3, 4, -2};
  short int arr1[N]{1, 2, 3, 4, -2};
  long int arr2[N]{1, 2, 3, 4, 5};
  Person p1[N]{{"someadd", 'a', 12}, {"random", 'b', 113}};
  byte_diff<int, N>(arr, arr + N);
  byte_diff<long int, N>(arr2, arr2 + N);
  byte_diff<short int, N>(arr1, arr1 + N);
  byte_diff<Person, 2>(p1, p1 + 2);
  return 0;
}

The given segment of code show that on my implementation the integers occupies 4 bytes or 32 bits, short int occupies 2 bytes or 16 bits, and char occupies a byte or 8 bits while pointer to const char and long int occupies 8 bytes or 64 bits.

Multi Diamenssional Arrays

A muti-dimenssional array can be considered as an array of an array, or colloquially but incorrectly speaking it can be considered as a matrix. I emphaisis on not calling such array matrix in literal context, mostly due to the fact that even though they represnet the equivalent of a matrix in mathematically sense, but there in no single matrix like entity stored in memory while defining multi-dimenssional array in CPP, the dimenssion corresponding to row and columns of such multi-dimenssional array exist in the compiler's memory only and its our job to somehow specify those dimenssions. A multi-dimenssional array is generally stored sequentially in a contiguous block of memory. much like a single dimenssional array or a normal array. As such since a multi-dimenssional array requires dimessions for both the no of array of the given type, no of element in each such array, we can traverse such array either by explicitly providing both dimenssions, or by providing the first dimenssion since, second dimenssion is usually always required to determine the length of the corresponding sequential array. Therfore one can traverse a multi-dimenssional array as follows:


#include<iostream>
using namespace std;
void TwoDArrayTraversal(int arr[2][3]){
  for(size_t i = 0; i < 2; i++){
    for(size_t j = 0; j < 3; j++){
      cout << arr[i][j] << " ";
    }
    cout << endl;
  }
}
void TwoDTraversal1(int arr[][3], int dim1){
  for(size_t i = 0; i < dim1; i++){
    for(size_t j = 0; j < 3; j++){
      cout << arr[i][j] << " ";
    }
    cout << endl;
  }
}
void TwoDTraversal2(int* arr, int dim1, int dim2){
  for(size_t i = 0; i < dim1; i++){
    for(size_t j = 0; j < dim2; j++){
      cout << arr[i * dim2 + j] << " ";
    }
    cout << endl;
  }
}
void ThreeDTraversal(int arr[2][2][3]){
  for(size_t i = 0; i < 3; i++){
    for(size_t j = 0; j < 2; j++){
      for(size_t k = 0; k < 3; k++){
        cout << arr[i][j][k] << " ";
      }
      cout << endl;
    }
    cout << endl;
  }
}
void ThreeDTraversal(int arr[4][4][2]){
  cout << __FUNCTION__ << " " << __LINE__ << endl;
  for(size_t i = 0; i < 4; i++){
    for(size_t j = 0; j < 4; j++){
      for(size_t k = 0; k < 2; k++){
        cout << arr[i][j][k] << " ";
      }
      cout << endl;
    }
    cout << endl;
  }
}
void ThreeDTraversal1(int arr[][4][2], int dim1){
  for(size_t i = 0; i < dim1; i++){
    for(size_t j = 0; j < 4; j++){
      for(size_t k = 0; k < 2; k++){
        cout << arr[i][j][k] << " ";
      }
      cout << endl;
    }
    cout << endl;
  }
}
void ThreeDTraversal2(int* arr, int dim1, int dim2, int dim3){
  for(size_t i = 0; i <dim1; i++){
    for(size_t j = 0; j < dim2; j++){
      for(size_t k = 0; k < dim3; k++){
        cout << arr[i * dim2 * dim3 + (j * dim3 + k)] << " ";
      }
      cout << endl;
    }
    cout << endl;
  }
}
int main(){
  int arr2D[2][3]{{1, 2, 3}, {4, 5, 6}};
  int arr3D[4][4][2]{{
    {1, 2}, {3, 4}, {5, 6}, {7, 8}
  }, {
    {9, 10}, {11, 12}, {13, 14}, {15, 16}
  }, {
    {17, 18}, {19, 20},{21, 22}, {23, 24}
  }, {
    {25, 26}, {27, 28}, {29, 30}, {31, 32}
  }};
  TwoDArrayTraversal(arr2D);
  TwoDTraversal1(arr2D, 2);
  TwoDTraversal2(&arr2D[0][0], 2, 3);
  ThreeDTraversal(arr3D);
  ThreeDTraversal1(arr3D, 4);
  ThreeDTraversal2(arr3D[0][0], 4, 4, 2);
}

If you skim through the above-mentioned code while taking a breif respite of reading, you might come to a comprehension that passing array by the value in the function while explicitly providing its size with the argument passed itself is often frowned due to the following reasons.

  • Using array as such is generally quite rigid and requires the user to correctly metioned the array bound while traversing the array for which case any mistake in the loop condition could result in reading or writing off the unrelated memory location thereby triggering hardware errors exceptions.
  • Moreover, Since array generally converts to the pointer to the first element either way, we cant really pass array by value to a function. Additionally speaking explicitly mentioning the bound within the array subsript itself as done in Traversal would restrict the user to only pass the array with same bound to the given function.
  • Consequently, whenever using array I often recommend using the pointer based notion for passing array argument to the function, even though the corresponding element access in such cases might seems a little verbose at first but it clearly specifies that intent that the array element are laid sequentially. Therfore, even if the user fail to access the corresponding array elelment within the well-specified range, it is rather less likely that any consequent operation would result in reading or writing off of the totally unrelated or far-fetched location in the memory.

Note the use of &arr2D[0][0] in the above mentioned code segment while traversing a Two Dimensional array. One could have equivalently written arr[0] as that would still reffer to the first array in the sequence and hence points towards the first element of the consequent first array or the start of the entire 2D array however, using just arr would result in a type error mostly pertaining to that the fact that arr would implicitly convert to the pointer to the pointer to the first elelment in the given sequence.

Pointer and const

C++ generally offers two related meaning of the construct constant. Though each of the corresponding construct might be seem in the same light and hence category by most of the programmer, they are constiderbly differnt language construct with relatively different meanings. The said mentioned language construct as as follows:

  • constexpr: used to signify that the fact that an expression can be evaluated only at compile time.
  • const: Use to signify the fact that an object or identifier can't be reassigned in the given scope.

Basically the constexpr's primary role is to ensure compile-time evaluation while a const is often used to specify immutability in the interface. This section is primarily concerned with the second role:

Many object don't have their value changed after intialization such object are often called as constant and they can usually be used to make the given code much more reliable, easier to reason about, and efficient to a certain degree. In general:

  • Symbolic constant leads to more maintainable code by using literals directly in the code.
  • Many pointer are often read through but never written through.
  • The read only object are often much more verbose in the value it can assume throughout its lifetime, and often free from any side-effect such subsequent change of the value before the inteded use in the program, etc. Moreover, a read only memory location usually doesn't require as much of house-keeping operations as a volatile memory location and is relatively faster to access and use.

While dealing with pointer and const however, we are generally concerned with two things:

  • The object pointed to by the given pointer.
  • The pointer itself.

As such prefixing the corresponding pointer with a const makes the object pointed to but not the pointer itself. If one want to make the pointer itself constant, one usually need to use the declrator operator *const instead of plain *.

For example consider the following code segment:


#include<iostream>
using namespace std;
void pointer_and_const(){
  int a = 12;
  int a1 = 13;
  const int* ptr = &a; //pointer to constant integer, we aren't allowed to modify *ptr in the given scope.
#ifdef errors
  *ptr =12;
#endif
  int* ptr1 = &a1;
#ifdef errors
  ptr1 = ptr;  //cant assign pointer to constant to an unrestricted pointer. Cause it allow for the modification of the corresponding value.
#endif
  ptr = ptr1; //OK can't modify ptr either way. Hence no harm can be done from assign a non-const pointer to a const pointer, as it would just not modify the corresponding value being pointed.
  int* const ptr3 = ptr1; //const pointer to an int. OK still can modify ptr3.
#ifdef errors
  int* const ptr4 = ptr; //Error cant modifidy *ptr;
#endif
#ifdef errors
  ptr3 = ptr;//error cant reassing a const qualified pointer.
#endif
  const char* something = "something"; //const pointer to const char.
  const char* const cc = something;  //OK will still not allow *something to be modified in the given scope.
#ifdef error
  cc = something; //cant reassign a const qualified pointer in a given scope.
#endif
#ifdef error
  *cc = "random"; //error cant modify the given object in the given scope.
#endif
  cout << *ptr << " " << *ptr1 << " " << *ptr3 << " " << something << " " << cc << endl;
}

int main(){
  pointer_and_const();
  return 0;
}

In the above mentioned example one can notice the following:

  • The notion of pointer to the constant is declared with the declrator const. There is no const equivalent for const pointer as that would considered as the part of base type meaning both const type* and type const* corresponds to the same pointers type i.e. a pointer to const object.
  • The pointer to constant object would not allow the object pointed to be modify in the given scope, whereas a constant pointer will point to the same object throughout its lifetime, as it can't be reassigned in the given scope.
  • Most of the people generally finds its helpfull to read the pointer with const notion from right to left consequent const char* somethign would inductively means the pointer to const char, while *const char means a const pointer to character.
  • A constant pointer to const object can be created as const typename* cosnt identifier, as such much like its name imply it is generally used to enforce strictest of the constness in the given scope, since a const pointer to a const object can't be change how so-ever plausbible.

Pointer and ownership

A Resource is something that is to be acquired in a program and later relase, for istance memory acquired on the free store by new and delete and file opned by fopen and eventually closed by flocse method on a file descriptor are all the example where the most direct handler to a resource is a pointer. This can however, be most confusing cause pointer are are often easily passed in the program and there is nothing in the type system that can identify a pointer that owns a resource from the one that doesn't. Consequently, consistently manaiging the resources hanlded with a pointer require a stringent strategy which can easily be implemented by employing the principal of RAII as opposed the frequent use of naked news and delete. More so, one can also try to put any pointer that is assumed to hold any resource in a resource handler such as a string, vector, unique_ptr, shared_ptr, valarray, etc., so as to distinguish any pointers owning the resource from teh one that doesn't. This sort of distinction is usualy imapartive cause trying to realse any object that doesn't hold any resource can lead to undefined behavior that are usually cathestropic, while not releasing the resources held by pointer after its last intended use can of course, lead to potential resource leaks, vitually affecting the program's performance at the runtime. For instnace let's consider the following chunk of code:

#include<iostream>
#include <string>
using namespace std;
class PointerAndResources{
  istream* is;
  bool owns;
  void close(){if(owns){delete is;}}
  public:
    void printSomething(){
      string s;
      std::getline(*is, s);
      cout << s << endl;
    }
    PointerAndResources(istream* is1): is{is1}, owns{true}{};
    PointerAndResources(istream& is1): is{&is1}, owns{false}{};
    ~PointerAndResources(){close();}
};
void confused(int* ptr){
  delete[] ptr;
}
void usePointerAndResources(){
  PointerAndResources p1{cin};
  p1.printSomething();
  int* ptr1 = new int[1024]{};
  confused(ptr1); //OK managing the lifetime of the object allocated on the free store by explicitly freeing the memory allocated to it before it last inteded use to prevent resource leaks.
  int ptr2[1024]{};
#ifdef errors
  confused(ptr2); //error calling delete on the statically allocate object whose lifetime is controlled automatic within the function scope in which it's being declared thereby leading to double deletion or call to free.
#endif
}
int main(){
  usePointerAndResources();
  return 0;
}

References

Reference are another way of indirection or accessing the corresponding object with it address and hence a descriptor point to that address rather than the copy of the particular object. In particular a reference could be thought of as a linkage to an object, but is not itself a object. One can consider a reference as a constant pointer which is implicitly referenced every time it is being used, as long as you remember that a pointer is an integral descriptor to an address holding the object, meaning that pointer itself could be considered as an object(a contiguous region of storage in memory), while a reference can't its just a linkage to the particular object.

Since the refernce is just a linkage to the object any kind of the operations performed on the objects are generally reflected on the object for which its a reference. More so a when dealing with the reference we're generally consist of two things:

  • Wheter the object for which its a reference is moveable, meaning it can be moved from it memory location therefore leaving the memory location in a valid but unspecified state.
  • Wheter the object has an identity, meaning it can be pointed to, reside in a memory location, and can often be assigned.

Based on the following two criteria a reference can be classified as follows:

  • (!i + m): An object that doesn't have an identity but is moveable is often called as rval.
  • (i): An object that has identity often called as genralize lval object.
  • (m) - An object that is moveable often called as pure rvalue object.
  • (i + !m): An object that has an identity but is not moveable, often know as lval object.
  • (i+ m): And a special construct for an object that has both identity and is explicitly moved by the user often know as an extraordinary rval of xval object, an xval object is often facilitates by the use of std::move() function as implemented in the standard library in CPP.

With few subtle but important difference reference could be considered as direect counterpart to pointers, but are usually more amicable to use due to the following reason:

  • They have much easier Syntatical notion and can be accessed much like any other object with the name of the reference itself, as opposed to using valueof opeartor on them like pointers.
  • The member for a reference to particular object can also be accessed in a relatively similar notion exploition memberof (.) dot oeprator as opposed to using indirection operators (->) on pointers.
  • There is no null reference and as such we may assume that a reference always refers an object.
  • Since no object are created while defining reference are are usually used in recursion to prevent a lot of copies of same object being created on the recursive stack. Consequently, it is usually preffered to pass large representation of objects by reference in recursive functions especially if its value is not going to change over the period of recursion.
  • Moreover, since, a reference is just a linkage to an actual object, they are usually used as a return type or argument to function in general and operators in particular. The following sections dicussed the given constructor in further details

Lval References

An lval Reference generally reffers to something that is used in the left hand side of an expression. Howeover, that is just a crude way describing an Lvalue, cause even though if it turn out to be not immidiately apparent an lval can also be used at the right hand side of an expression. In fact, an lval is generally something whose value you need to preserve after the inteded operation, Conseqeuently, an lval generally reffers to an object in memory that can pointed to, accessed, etc. An lval reference on the other hand is just a linkage to such value and hence is quite impartive in accessing such large values at a relatively cheaper cost cause we don't need to deal directly with the copy of the object.

An lvalue can only bind to another lvalue hence, assign an rval value to it i.e. a temporory or intermidiate result of compuation such as the return value from a function is often not allowed.


#include<iostream>
#include<vector>
using namespace std;
struct Person{
  int a;
  float b;
  string s;
};
vector<int>v1(12, 2);
void lvalreference(vector<int>& large_obj, Person& larger_obj){
  //use reference for expensive to copy object.
  cout << sizeof(large_obj) << " " << sizeof(larger_obj) << endl;
}
void next(int& val){
  val += 1;
}
int next1(int& n){
  int val = n + 1;
  return val;
}
string return_string(){
  return "temporory from a function";
}
int main(){
  vector<int>v1{1, 2, 3, 4};
#ifdef Error
  lvalreference(v1, Person{1, 2.2f, "random"}); //error cant bind an rval here(temporory) with an lval reference.
#endif
  int values = 12;
  next(values); //obscure change in the value with change in the reference, rather return the change value.
  cout << values << endl;
  int values1 = next1(values); //more clearer in its intent i.e. incrementing the value and returing it.
  cout << values << " " << values1 << endl;
#ifdef Error
  string& s = return_string(); //Error cant bind an rval(return value from a function) with an lval.
#endif
  int& refval = values;
  values = refval + 12; //using lval reference on right Hand side of an expression.
  return 0;
}

Rval References

An Rvalue reference is generally used to refere to something that can be used only only the rightHand side of an expression, however much like an lval reference this has a rather loose interpretation cause we can use rval at right hand side of an expression as well. In general rvals are used for optimization purposes to convert the copy operation for temporory or intermidiate values, which we don't need to preserve after the inteded operation to rval. This notion is often supported by std::library move construct however, with subtle difference that it is explicity used to move an lval(i.e. having an identity) as opposed to an rvalue. Such sort of values that are explicity moved or rather converted to rval values despite being lvalues are called as xlvalues. Rval in that sense also allow perfect forwording and usually used with move constructor ofcourse, while an lval are usually used with a copy constructor to pass a large representation of an object at a relatively cheaper cost. As such much like lvalues an rvalues can't be bind to an lval and are generally bound to single object throughout their lifetime, with same intrinsic behavior as regular references, i.e. any operation performed on such object will be reflected on the object itself.


#include<iostream>
#include<algorithm>
using namespace std;
#include<vector>
string return_res(){
  return "temporory values";
}
string rvalrefrences(string& fname, string&& lname){
  return fname + " " + lname;
}
template<class T>
void swaping(T& a, T& b){
  T temp = static_cast<T&&>(a); //converting an lval to rval similar to std::move(lval);
  a = static_cast<T&&>(b);
  b = temp;
}
void rval_reference(){
  int&& rvalref = 12;
  cout << rvalref << endl;
  rvalref += 1;
  cout << rvalref << endl;
  string&& res = return_res();
  res[2] = 'R'; //change expensive copy operation for large object to move operation for temporory one.
  cout << res << endl;
  string fname ="random";
  cout << rvalrefrences(fname, "age"); //OK
  #ifdef Errors
  cout << rvalrefrences("age", fname); //error cant bind an rval to lval.
  #endif
  vector<int>v1{1, 2, 3, 4};
  vector<int>v2(2, 0);
  swaping(v1, v2);
  for(auto& i: v1){
    cout << "first:: " << i << endl;
  }
  for(auto& i: v1){
    cout << "second :: " << i << endl;
  }
  v1 = std::move(std::vector<int>{});
  cout << (v1.size() == 0 ? "True" : "False") << endl;
}
int main(){
  rval_reference();
  return 0;
}

Const lval Reference

Sometime we dont want to change the value of reference being passed as a function argument or returned from the function for which case we cant use a const lval references, i.e. const lva&. As of now however, we don't really need a notion of a const rval references cause rval references are generally intended to change the refered values either value. However, since no harm can be done by using still making it const, a const lval reference can bind both lval reference and rval references. As such a reference can also be used as a return type of the function, this notion of using references as a return value from a function is generally used when an value can be used at both left and right hand side of an operand such as the return value of the subsscripting operator can be used on both left and right hand side of an expression.


#include<iostream>
#include<vector>
using namespace std;
string const_lval_reference(const string& fname, const string& lname){
  return fname + " " + lname;
}
template<class K, class V>
class Map{
  private:
    vector<std::pair<K, V>>elem;
  public:
    std::pair<K, V>* begin(){
      return &elem[0];
    }
    std::pair<K, V>* end(){
      return &elem[elem.size()];
    }
    V& operator[](const K& key){
      for(auto& i: elem){
        if(i.first == key){
          return i.second;
        }
      }
      elem.push_back(std::pair<K, V>{key, V{}});
      return elem.back().second;
    }
    friend ostream& operator<<(ostream& os, Map<K, V>& m1){
      for(auto& i: m1.elem){
        os << i.first << " " << i.second << endl;
      }
      return os;
    }
  Map(std::vector<std::pair<K, V>>& m1): elem{m1}{};
  Map(std::vector<std::pair<K, V>>&& m1): elem{std::move(m1)}{};
  Map(): elem{std::vector<std::pair<K, V>>{std::pair<K, V>{K{}, V{}}}}{};
};
int main(){
  string first = "random";
  string second = "something";
  cout << const_lval_reference("first", "second") << endl;
  cout << const_lval_reference(first, second) << endl; //OK const lval can bind both lval reference and rval references.
  cout << const_lval_reference("first", second) << endl; //OK const lval reference can bind both rval and lval references.
  Map<int, string>m1{vector<std::pair<int, string>>{{1, "random"}, {2, "age"}, {3, "random age"}}};
  cout << m1 << endl;
  cout << m1[3] << endl;
  cout << m1[123] << endl;
  Map<int, string>m2{};
  for(auto& i: m2){cout << i.first << " " << i.second << endl;}
  return 0;
}

References to References

Since C++ only support lval reference, rval reference and const lval reference, the notion to reference to reference can only be undertaken by providing type specifier in the type alias. For instance let us consider the following:


#include<iostream>
using namespace std;
template<class T>
void reference_to_reference(){
  using lval = T&;
  cout << typeid(lval).name() << " "; //i ? Reference are nevery implicity dereferenced in an expression meaning their is no object of reference type but, object can only be of base type for which they are reference.
  using lvalrvalref = lval&&;
  cout << typeid(lvalrvalref).name() << " ";
  using lvallvalref = lval&;
  cout << typeid(lvallvalref).name() << " ";
  using lvalrvalrvalref = lvalrvalref&&;
  cout << typeid(lvalrvalrvalref).name() << " ";
}
int main(){
  reference_to_reference<int>();
  return 0;
}

However, since nothing can change the fact that only lval can be assigned to lval, meaning all of the reference to reference type collapsed to laval, this is often known as reference collapse.

Pointer and References

Since reference and pointer relatively similar construct of refering an object through indirection with memory address as oppose to the copy of the object, we can almost use any of them interchangibly except for a few places as follows:

  • A reference is linked to a single object for which it has been declared as a reference however, a pointer may point to different object throughout its lifetime.
  • Since a reference is not an object itself, we can't use it for any construct that requires object, for instance we can't have array of references or any container of refernce cause they are used to specify sequence of objects.
  • Since a reference is linked to single object throughout its lifetime and cant be used for object, if you need to traverse a sequence where each object is at a different memory location, you should use pointer.
  • References are specifically used as argument or return value to a function in particular an operator defined on a given type. This in turn facilitate us to provide the notion for operators on objects of given types with a rather direct notion using object name itself, as oppose to passing pointer to that object.
  • There is no equivalent of nullptr in reference, thus if you want invalid representation you should use pointer as opposed to a reference. Though we can go out of our way to define null references, however, such declration often stands out as a sore thumb they are and are usually considered illegal with most of the code optimizer, however, even if you current implementation of compiler don't grumble about using such declration, you should define a null reference.

#include<iostream>
#include<limits>
using namespace std;
void linked_to_Same_object(int& refval){
  while(refval > 0){refval += 1; }
  int somevalues = 13;
  refval = somevalues;
  cout << refval << endl;
  int someothervalues = 14;
  refval = someothervalues;
  cout << refval << endl;
}
void link_to_different_objects(){
  int* ptr = nullptr; //used to create notion of pointer pointing to nothing.
  int arr[1024]{1, 2, 3}; //pointing to different object in sequences.
  int sum = 0;
  for(ptr = arr; ptr != &arr[1023]; ptr++){
    if(*ptr == 0){sum += 1;}
  }
  int r = 13;
  ptr = &r; //Pointing to different object now.
  cout << "no. of default values:: " << sum << endl;
}
template<class T, size_t N>
class someMatrix{
    private:
      T* elem;
    public:
      someMatrix<T, N>* operator+(someMatrix<T, N>* m1){
        for(size_t i = 0; i < N; i++){
          elem[i] += m1->elem[i];
        }
        return this;
      }
      someMatrix(T* elem1): elem{elem1}{};
    friend ostream& operator <<(ostream& os, someMatrix<T, N>* m1){
      for(size_t i = 0; i < N; i++){
        os << m1->elem[i] << " ";
      }
      return os;
    }

};
template<class T,size_t N>
class Matrix1{
  T* elem;
  public:
    Matrix1(T* elem1): elem{elem1}{};
    size_t size(){return N;}
    friend ostream& operator<<(ostream& os, Matrix1<T, N>& m1){
      for(size_t i = 0; i < m1.size(); i++){os << m1.elem[i] << " "; }
      return os;
    }
    T* begin(){return &elem[0];}
    T* end(){return &elem[N-1]; }
    Matrix1<T, N>& operator+(Matrix1<T, N>& m1){
      for(size_t i = 0; i < N; i++){
        elem[i] += m1.elem[i];
      }
      return *this;
    }
};
void use_Matrix(){
  int arr[3]{1, 2, 3};
  someMatrix<int, 3> m1{arr};
  someMatrix<int, 3> m2{arr};
  someMatrix<int, 3>* m3 = m1 + &m2; //uggh...
  cout << m3 << endl; //arr is changed since the we are passing pointer to the given object not creating new object in to be passed as copy to constructor argument.
  int arr1[3]{1, 2, 3};
  Matrix1<int, 3>m4{arr1};
  Matrix1<int, 3>m5{arr1};
  cout << (m4 + m5) << endl;
}
int* refernull(int* ptr){
  return ptr;
}
void nullreferences(){
  int& ref{*refernull(nullptr)};
#ifdef dont_use_null_refernces
  cout << ref << endl;
#endif
}
int main(){
  int r = std::numeric_limits<int>::max();
  cout << r << endl;
  linked_to_Same_object(r);
  cout << r << endl;
  link_to_different_objects();
  use_Matrix();
  nullreferences();
  return 0;
}
This Blog was created with sole intent of teaching and learning programming as such, this is a sole ditch effort, so please forgive any mistakes senpais. For further suggestion please contact via Email in the footer! 😁