-2

I am creating my own version of the vector container (patterned after the STL vector container) and have come across an interesting issue. I want to be able to assign values to my vector using the .at() function like:

vector<int> myVec1 = {1, 1, 3, 4};
myVec1.at(1) = 2; //assigning a value using .at()

So I did some experimenting, and I found that if I make my .at() function like this:

template <typename Type>
Type& My_vector<Type>::at(int const index) {            //notice the ampersand(&)
    return *(array + index);
}

then I am able to assign values just like in the STL vector class! I put the exclamation mark because to me, this is a surprise. My thoughts were that this use of the & operator would return the address of the value at the given index, and that doing something like:

My_vector<int> myVec1 = {1, 1, 3, 4};
myVec1.at(1) = 2;

would set the address of the second element of myVec1 to 2. This is not the case however, instead it changes the value there. This is good for me because my goal is to be able to assign a value using .at(), but my question is, what is the flaw in my thinking when I say that this function should return an address of the value at this point? How is the ampersand operator in my function definition actually working here? Can I say "it is returning a reference to the value of the array at the given index?" This seems to be what is happening, but wow, I thought I had a good grasp of how the & and * operators worked in most situations. Here is a fully functional example:

My_vector.h

#ifndef My_vector_h
#define My_vector_h

#include <cstring>
#include <type_traits>
#include <initializer_list>

template <typename Type>
class My_vector {
private:
    Type* array;
    int vector_size;
public:
    //Copy Constructor
    My_vector(std::initializer_list<Type> list) {
        array = new Type[list.size() + 10];
        vector_size = list.size();
        memcpy(array, list.begin(), sizeof(Type) * list.size());
    }

    //Destructor
    ~My_vector() {delete [] array; array = nullptr;}

    //Accessor .at()
    int size() {return vector_size;}
    Type& at(int const);
};

template <typename Type>
Type& My_vector<Type>::at(int const index) {
    return *(array + index);
}

#endif /* My_vector_h */

main.cpp

#include <iostream>
#include "My_vector.h"

int main() {
    My_vector<int> sampleVec = {1, 1, 3, 4};

    for (int i = 0; i < sampleVec.size(); i++) {
        std::cout << sampleVec.at(i) << " ";
    }
    std::cout << std::endl;

    sampleVec.at(1) = 2;

    for (int i = 0; i < sampleVec.size(); i++) {
        std::cout << sampleVec.at(i) << " ";
    }
    std::cout << std::endl;

    return 0;
}

Output:

1 1 3 4 
1 2 3 4 
KhanKhuu
  • 177
  • 11
  • 2
    references are not the same thing as addresses - the `&` used to declare a reference is not the same thing as the `&` used to take the address of something –  Oct 28 '18 at 20:41
  • 2
    The `&` operator has two distinct meanings in C++. The `address-of` has nothing to do with `reference-to` and vice-versa. It's confusing that `&` means two things, but that's the way it is. – PaulMcKenzie Oct 28 '18 at 20:42
  • 1
    @Paul Three things, actually. And the second you mention is not an operator. –  Oct 28 '18 at 20:42
  • @NeilButterworth Yes, plus `bitwise-and`. – PaulMcKenzie Oct 28 '18 at 20:43
  • @PaulMcKenzie this `reference-to` is allowing me to modify the value it returns? Is there any way that I can store this `reference-to` in a variable and continue to be able to modify it whenever I please? Or is a reference only good once when it is returned through the function? – KhanKhuu Oct 28 '18 at 20:45
  • 1
    All of this should be covered in your C++ textbook. –  Oct 28 '18 at 20:46
  • 1
    @KhanKhuu Yes, `int& foo = simpleVec.at(2);` initializes a reference named `foo` to element `2` of `simpleVec`. Keep in mind that references do not extend the lifetime of the referenced object, so this can be dangerous if you keep the reference around for longer than the referenced object exists. – Miles Budnek Oct 28 '18 at 20:48
  • 1
    @KhanKhuu Also, `&` used as `address-of` or as `bitwise-and` are holdovers from the `C` language. Using `&` as `reference-to` is purely a C++ invention (references do not exist in `C`). – PaulMcKenzie Oct 28 '18 at 20:53
  • @MilesBudnek this is very useful, thanks for the explanation – KhanKhuu Oct 28 '18 at 21:02

2 Answers2

2

Why Does Using the Reference Operator(&) ...

It's not a "reference operator". It is not an operator at all. You've declared the return type to be a reference type. Lvalue reference to be more specific.

then I am able to assign values just like in the STL vector class! I put the exclamation mark because to me, this is a surprise.

If you take a look at the return type of the standard library vector at function, you'll find that it also returns a reference.

My thoughts were that this use of the & operator would return the address of the value at the given index

It doesn't. It returns a reference. (Although, technically the compiler may indeed implement the indirection by using the memory address)

and that [myVec1.at(1) = 2;] would set the address of the second element of myVec1 to 2.

You assumed wrongly. Assigning to a reference is an assignment to the referred object (it is not possible to set a reference to refer to another object).

Can I say "it is returning a reference to the value of the array at the given index?"

Yes.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    Some reading to help understand some of the terminology used above: [What are rvalues, lvalues, xvalues, glvalues, and prvalues?](https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues) – user4581301 Oct 28 '18 at 20:50
1

There are a limited number of internationally usable symbols so c++ has to reuse some in different places.

In the case of ampersand there are three places it is used.

  1. The ampersand (address of) operator, this is used in an expression to retrieve the address of a variable and is the inverse of the star or dereference operator. E.g:

    int i = 1;
    int* pi = &i; // store the address of i in pi
    int j = *pi; // dereference the pi pointer and store the value in j
    
  2. A reference type, this is used in type declarations to declare a type which acts like a pointer but doesn't need dereferencing and can't be null. E.g:

    int i = 1;
    int & ri = i; // take a reference to i in variable ri
    int j = ri; // store the value of the referenced variable in j
    
  3. Bitwise and operator, this does the binary and operation on two numbers. E.g.:

    int i = 3;
    int j = 2;
    int k = i & j; // set k to 3 AND 2
    

The two operators can also be overloaded in classes to do something else (though this should be used with caution).

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60