lvalue required as left operand of assignment in C++ class

Pages: 12
Hi. Can someone please point me where and why am I making the lvalue required as left operand of assignment error in the following code?



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
#include <iostream>
#include <vector>

class sample {
  double *value = nullptr;
  int X = 0;

public:
  sample() = default;
  ~sample() {
    if (value)
      delete[] value;
  }
  sample(int x) : value{new double[x]}, X{x} {}
  int size() const { return X; }
  double operator[](int n) const { 

    return value[n]; 

    }
};

void Samplefunction(sample &x) {
  for (int n = 0; n < x.size(); ++n) {
    x[n] = n*6;
  }
} 


Last edited on
The operator on line 16 does not allow assignment (it is actually const).

The operator that accept assigning to would look like this:

double& operator[](int n) { return value[n]; }

Note the reference as the result type and that there is no const.
Exactly! I got it.......You're great! thanks :-).....marking as solved
Usually you would provide both as overloads. The compiler will then choose the appropriate one.
@seeplus: thanks for your input. Can you please elaborate how?
Hi,
the Mr (compiler) tells you that only memory space (lvalue) can contain the result of an expression.

The problem is in line 25 of your code which originates from the return type of the overload of the index operator []. In your code the overload of your index operator [] returns a copy (here rvalue) of the element with index n, but a rvalue is a value and a value can't contain another value. (did you understand your mistake?)

But let's find the solution to your problem:
The solution is simple, just add the address-of & operator to the return type of the overload of your index operator []. So to say that the overload of your index [] operator should not return a copy of a value but a reference of the element located at the desired index

Ex:
double& operateor[](int n) const;

Note: why do you use a bare pointer? it's not recommended in C++ unless you have a good knowledge of the subject, use a smart-pointer instead.

I see you want to reimplement the shared_ptr class model?
Last edited on
Jos, we see a lot of students here who are taught to use c-strings, c-arrays, c-pointers. Its good to show them the modern alternates, but that is what is the why you are asking about here and in your other postings. And often enough, they are not even allowed to use the 'new' stuff. Some (many) schools are still in 1995.
@EL Jos: thanks for your comprehensive feedback.

In your code the overload of your index operator [] returns a copy (here rvalue) of the element with index n, but a rvalue is a value and a value can't contain another value.

Yes, precisely! at this point I have been assigning a value to another value.....got it :-)


Note: why do you use a bare pointer? it's not recommended in C++ unless you have a good knowledge of the subject, use a smart-pointer instead.

No specific reason for the base pointer. And I agree, there is a way with smart-pointer as well. Thanks :-)
Per @Jonnin's request,
@BZKN, here is your code using modern C++.

Some modification:
1. the type of your attribute x which is used as an index and your parameter n should not be ints but rather size_t which accepts much larger values and makes your code portable in different OS.

2. I used here a smart pointer of type std::shared_ptr because I saw in your destructor, you would free the resource only if the pointer is valid. Also a std::shared_ptr is much easier to use than a std::unique_ptr.

3. Since you allow random access to your elements you should check if the user of your class will give an index outside the range of elements pointed by your pointer.
I'll let you do that as homework ;)

here is the code with modern C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <memory>

class sample {
  std::shared_ptr<double[]> value;
  const size_t X = 0;

public:
  sample() = default;
  ~sample() = default;
  sample(size_t x) : value{std::make_shared<double[]>(x)}, X{x} {}
  size_t size() const { return X; }
  double& operator[](size_t n) const { 

    return value[n]; 

    }
};

void Samplefunction(sample &x) {
  for (size_t n = 0; n < x.size(); ++n) {
    x[n] = n*6;
  }
}
Last edited on

@Jonin: thanks for compelling EL Jos to write an alternate ;)

@EL Jos: ok this is interesting.....because earlier my destructor indeed had old school style memory deallocation....so I'm guilty ;)

Since you allow random access to your elements you should check if the user of your class will give an index outside the range of elements pointed by your pointer


I will carry on from this point now ..... ;)
Last edited on
@seeplus: thanks for your input. Can you please elaborate how?


1
2
double operator[](int n) const { return value[n];  }
double& operator[](int n) { return value[n]; }


@jos - L13. Not really. if you define as const, return a value otherwise if not const then return a ref.

@BKZN - re the OP. You don't need L11. You can pass a nullptr to delete in which case it is ignored.

Also note that if you use dynamic memory then you should also provide copy constructor (and preferably move constructor as well) and copy/move assignment (operator=() ). If you don't and a copy or assignment is undertaken then a shallow copy/assignment (just copy the contents of the pointer) is undertaken which is not what is required. A deep copy is needed (the copying of the data pointed to to a new location).
Last edited on
@seeplus,

Yes I know, this is part of the traps that I left expressly, so that he corrects it as homework, because we can not do everything for him, otherwise it is no longer help.

Apart from that, if you look carefully, there is also something else that I left expressly so that the author of the question develops his sense of observation and curiosity ;)
@jos - I don't agree with leaving 'traps' for the unwary in code without stating that trap(s) have been left. Others reading the code may take it as correct.
@seeplus,

Yes I understand you don't worry, the traps are not very serious and you know it ;)
@EL Jos

Just FYI, it's not a homework ;). This is part of my C++ practice/learning/playground activity going on in full swing nowadays...... nothing related to academics or homework :)
Last edited on
Oh, if its not limited to classroom...
is there any reason you do not replace that pointer with a vector entirely?

@ Jonnin....no reason. Of course replacing vector makes more sense.

Why using pointer? Because as you have seen, this is where I need to get strong....Hence been practicing pointers.....
Fair enough. I still call that academics, and in that case, I actually may be the odd guy out but I DO recommend learning raw pointers alongside the smart ones. You will on occasion run into C or older C++ that uses them heavily and it won't hurt to run through them.

Also, the other piece of advice I would give is to split it into two topics as you study.
1) dynamic memory
2) pointers

dynamic memory involves pointers as the mechanism for getting it done and its a modest topic all by itself. But pointers can do so many other weird things that the second topic is big.

a few basic things you can do with pointers that have nothing to do with dynamic memory:
- they can act very similar to a reference (and can be rebound)
- you can do a type of range based for loop with them
- you can use them to do the 'union hack' for example casting a double to bytes so you can get the sign bit or change it
- or more general of the above, you can cast any pointer to any other pointer type, eg an array of somestructs to an array of raw bytes: you have seen this when you write() to a binary file perhaps, you cast the input into a char *
- you can use them to invoke functions
-- and more
With copy/move constructor and assignment, then possibly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class sample {
    double* value {};
    size_t X {};

public:
    sample() = default;
    ~sample() { delete[] value; }
    sample(size_t x) : value {new double[x] {}}, X {x} {}
    sample(const sample& s) : X(s.X), value {new double[s.X]} {std::copy_n(s.value, X, value); }
    sample(sample&& s) noexcept : X(s.X), value(s.value) { s.value = nullptr; }

    size_t size() const { return X; }
    double operator[](size_t n) const { return value[n]; }
    double& operator[](size_t n) { return value[n]; }

    sample& operator=(sample s) noexcept {
        X = s.X;
        std::swap(value, s.value);
    }
};

Pages: 12