1

I have two versions of a function. One returns by value, intended for use as an r-value. The other returns by reference, intended for use as an l-value (to write to the audio_data vector).

float AudioData::sample(unsigned frame, unsigned channel) const
{
    return audio_data[frame * channel_count + channel];
}

float& AudioData::sample(unsigned frame, unsigned channel)
{
    clearSplits(); // Clear out cached information that may be invalidated by this call
    return audio_data[frame * channel_count + channel];
}

While debugging, I've noticed that the reference-returning override is getting used as an r-value:

const float *AudioData::split(unsigned channel)
{
    if (splits[channel] != nullptr) return splits[channel];
    if (channel_count == 1) return data();

    float *split = new float[frame_count];

    for (unsigned i = 0; i < frame_count; ++i)
    {
        split[i] = sample(i, channel); // Calls the reference-returning override
    }

    splits[channel] = split;
    return split;
}
void AudioData::clearSplits() // This gets called while writing to the splits!
{
    for (unsigned i = 0; i < splits.size(); ++i)
    {
        if (splits[i] != nullptr) delete[] splits[i];
        splits[i] = nullptr;
    }
}

Why does the compiler opt to use the non-const l-value override when it's not modifying the return reference and how can I prevent it from doing so?

Ott
  • 57
  • 8

1 Answers1

1

Overload resolution does not care at all about the return type of your function. All it cares about are the types of arguments to the call. And in your case, since you are calling sample from split (a function that is not const-qualified), the implicit object argument (this) is not const qualified. As such the non-const overload gets called, and it returns a reference.

An easy solution is to provide another function, by another name, that returns a copy too.

float AudioData::csample(unsigned frame, unsigned channel) const
{
    return sample(fram, channel); // will always call the const overload
}

If you are averse to that, then you can always const_cast your way to calling the correct overload:

split[i] = const_cast<AudioData const *>(this)->sample(i, channel);
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thanks. I knew that that the return value wasn't considered when choosing between overloads but I didn't know that the only think it looks at when comparing const-ness is the const-ness of the calling function. – Ott Oct 11 '19 at 23:09
  • @Ott It does not look at the constness of the calling function. It looks at the constness of the object whose method is called, i.e. "this" (basically "argument zero" of the called function). This is in turn is affected by the constness of "split", because it is a member of the same class as "sample", so the object happens to be the same for both. Non-members can not be const-qualified. – Larry Oct 11 '19 at 23:34