Hari Honor
Hari Honor

Reputation: 8924

"Assign to Indexed" C++ Operator Overload Brainteaser

I'm writing some funky audio code and trying to use operator overloading to create a very clean and simple API. It's become of a bit of a C++ brainteaser...

What I want would be solved immediately by an "assign to indexed" sort-of-compound operator which I'm pretty sure doesn't exist. Might anyone have any insights on whether the following is possible?

I have 2 object types....

Frames frames;  // audio data, contains 1 buffer (float *) for each channel
Sample s;       // a single sample, 1 float for each channel

So Sample is kind of an orthogonal slice of Frames, ie Frames is NOT an array of Sample's. If you know audio, Frames is "non-interleaved", and Sample is.

The Holy Grail...

s = frames[1];    // statement 1. gets a copy of values in frame 1
frames[1] = s;    // statement 2. replace values in frame 1 with those in Sample s

The first one is no problem:

// in class Frames...
Sample& operator[](size_t idx) const { 
    Sample s;
    s.left = _leftCh[idx];
    s.right = _rightCh[idx];
    return s;
}

But the second assignment is tricky since the above function creates a copy of the data rather than a reference.

I've tried defining Sample with references...

class Sample {
public:
    float& L;
    float& R;
    Sample(float& lt, float& rt) : L(lt), R(rt) {};
}

But then you can't do something as simple as...

Sample s(0.0, 0.0);
s.left = 0.2;

Another potential solution would be to have the two statements call two different operator overloads. Then enforce that statement 2 calls this [] overload which returns a new Frames object pointing to the values rather than a Sample object:

Frames& operator[](size_t idx) {
    // Construct an new Frames object whose which 
    // points to value idx in each channel
    Frames f(_size - idx);
    f._leftCh = &_leftCh[idx];
    f._rightCh = &_rightCh[idx];
    return f;
}

And then add an assignment operator to Frames which just replaces the first value...

Frames& operator=(const Sample& s) {
    _leftCh[0] = s.left;
    _rightCh[0] = s.right;
    return *this;
}

The compiler informs me that methods must differ by more than just the return type, but this is resolved by having const after the method name for one of the operator[] overloads. Might there be a clue here? Is there a way to have statement 1 call Sample& operator[]... and statment 2 call Frames& operator[].... Or is there a much better way of accomplishing this??

Thanks for your patience if you've made it this far! Much appreciated...

Upvotes: 3

Views: 195

Answers (2)

Yimin Rong
Yimin Rong

Reputation: 2027

Have you tried without overloading just to work out the details? e.g. sample = frame.getSample(); frame.setSample(sample); Once the details are worked out to your satisfaction, then you can add the syntactic sugar and overload the [] and = operators.

It looks like you want to maintain a reference to the original Sample, so that for example:

sample.right = oldR;
sample.left = oldL;
f[x] = sample;

sample.right = newR;
sample.left = newL;
newSample = f[x];

assert(sample.right == newSample.right && sample.left == newSample.left);

Is this correct? If so, I don't think you can do that, because you "break" up your sample to insert it into the frame, so you lose the original connection.

Upvotes: 0

How about this:

class SampleRef {
  float &left_, &right_;

public:
  SampleRef(float& left, float& right)
    : left_(left), right_(right)
  {}

  operator Sample () {
    return Sample(left_, right_);
  }

  SampleRef& operator= (const Sample &arg) {
    left_ = arg.left;
    right_ = arg.right;
    return *this
  }
};

SampleRef Frames::operator[] (size_t idx) {
  return SampleRef(_leftCh[idx], _rightCh[idx]);
}

You can of course also add a const overload of operator[] which will simply return a Sample:

Sample Frames::operator[] (size_t idx) const {
  return Sample(_leftCh[idx], _rightCh[idx]);
}

Upvotes: 5

Related Questions