Const references with and without type conversion - why feature type conversion at all? - C++

220 Views Asked by At

I began learning C++ this week, and currently I am reading about compound types and constant variables. Unlike in most cases, references to const support type conversion by creating a temporary variable. But if so, then what's the difference in behaviour between:

int i = 42;
double di = 42;

and

int i = 42;
const double &di = 42;

Don't we end up with two independent variables that can end up having different values if we try to change i? Is the only difference that in the example with the const reference, the reference cannot be changed? The thing that bugs me the most is that when the types of a non-const variable and a const ref match, the reference points to the same address in memory and changes along with the change in the original variable, whereas this does not happen for a non-typematching const ref to a non-const variable:

#include <iostream>

int main() {
    int i = 42;
    const int &ri = i;
    const double &dri = i;
    
    ++i;
    std::cout << " at " << &i << ", " << ri << " at "
              << &ri << ", " << dri << " at " << &dri << std::endl;

    int j = i;
    int jj = ri;
    int djj = dri;
    std::cout << j << " at " << &j << ", " << jj << " at "
              << &jj << ", " << djj << " at " << &dri << std::endl;
    return 0;
}

Output:

43 at %Address1%, 43 at %Address1%, 42 at %Address2%
43 at %Address3%, 43 at %Address4%, 42 at %Address2%

This seems to me like a major difference in behavior that is easy to overlook from simply looking at the syntax, on top of the fact that such behavior seems counter-intuitive to the entire idea of references. Also, why does jj is allocated a separate space, but not djj, which references the same address as dri?

1

There are 1 best solutions below

0
Nicol Bolas On

Let's say you have a function of the form:

void foo(double const& d);

And now, let's say you have a float somewhere. And you want to pass that to this function via foo(f);. If a T const& could not bind to any object convertible to T, then this wouldn't work. Every user of this function that don't have a double would have to do foo(static_cast<double>(f)) or an equivalent.

You might say that maybe foo should take double by value. And for double specifically, maybe it should.

But what about if it's std::string, and I want to call foo("some string"). Well, "some string" is not a std::string; it is a string literal which is convertible to std::string. So we allow that conversion.

Again, you might say that it should take the string by value. But what about the cases when the caller really does have a std::string? They'd have to copy that string, a copy that is discarded and is therefore unnecessary.

Of course, C++'s rules should be uniform. So if we want this to work for function arguments&parameters, it also has to work for named variables. But even then, it could be useful. You might call a function that you expect to return a string of some form, but aren't especially picky about which form. Just so long as it is convertible to a std::string. This might be in template code:

template<typename T>
void foo(T t)
{
  std::string const& data = t.get_a_string();
}

Do you really care if get_a_string returns std::string exactly, or just some string type convertible to std::string? Probably the latter.