0

In this code, why is

  1. the constness of GetAutoRef and GetAutoRefJ's return values different,
  2. the return value of GetDecltypeAutoJ not const?
#include <type_traits>

struct A {
    int i;
    int& j = i;

    decltype(auto) GetDecltypeAuto() const { return i; }
    auto           GetAuto        () const { return i; }
    auto&          GetAutoRef     () const { return i; }

    decltype(auto) GetDecltypeAutoJ() const { return j; }
    auto           GetAutoJ        () const { return j; }
    auto&          GetAutoRefJ     () const { return j; }
};

int main() {
 A a{5};
 static_assert(std::is_same_v<decltype(a.GetDecltypeAuto()), int>       );
 static_assert(std::is_same_v<decltype(a.GetAuto()        ), int>       );
 static_assert(std::is_same_v<decltype(a.GetAutoRef()), const int&>); //as expected

 static_assert(std::is_same_v<decltype(a.GetDecltypeAutoJ()), int&>); // no const?
 static_assert(std::is_same_v<decltype(a.GetAutoJ()        ), int> );
 static_assert(std::is_same_v<decltype(a.GetAutoRefJ()     ), int&>); // no const?
}

Shouldn't j be const if accessed through the const this pointer in the J functions?

https://godbolt.org/z/3v4PKG5n3

Felix Dombek
  • 13,664
  • 17
  • 79
  • 131
  • You can't have a const reference, which is what the `const` of the member function applies to. You can see this by changing the value of `j` in your const functions and the compiler will not complain. – NathanOliver Jan 25 '23 at 22:48
  • Ah, of course ... the same difference of `const char *` and `char * const` -- the pointee is not const. But make it an answer! – Felix Dombek Jan 25 '23 at 22:51
  • Related: [Modifying reference member from const member function in C++](https://stackoverflow.com/q/2431596/364696) (though in that case it's mutating something outside the class, not part of the class, so not exactly the same case) – ShadowRanger Jan 25 '23 at 23:05
  • Also related: [const method modifies object using reference](https://stackoverflow.com/q/50836161/364696) (a similar sort of weirdness where a `const` member function mutates the object, by mutating it through a reference) – ShadowRanger Jan 25 '23 at 23:16

2 Answers2

3

You can't apply const to a reference, and that is what the const of the member function tries to apply const to. So what you are getting is the type of j which will always be int& for decltype(auto) or int when just using auto.

You can see this by changing the value of j in your const functions and the compiler will not complain.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • "You can't have a `const` reference, which is what the `const` of the member function applies to." doesn't sound right, even if it's technically correct (in the sense that a reference is *always* initialized, never reassigned, so it's impossible to mutate the reference itself, and therefore it's sort of "always constant but never `const`"). I feel like there has to be a better way to express this... (No, I'm not sure what it is) – ShadowRanger Jan 25 '23 at 23:15
  • @ShadowRanger I tried updating the wording a little. Does it read better now? – NathanOliver Jan 25 '23 at 23:20
  • Better. Maybe expand the first clause to "You can't apply `const` to a reference itself (only to the reference*d* object),", to clearly draw the distinction between reference and reference*d*. I know C++ fairly well, but (not being insane) I don't use raw references as instance attributes, so the exact interaction of `const` member functions and references (which can refer to `const` things, but are themselves sort of above the whole concept of const-ness) is new to me. – ShadowRanger Jan 25 '23 at 23:40
  • I can't imagine why anyone would do this. Now I'm wondering if, were `A` itself were truly `const` it would be undefined if it `GetAutoRefJ` were used to acquire the mutable reference and alter it; [clang and gcc "work"](https://godbolt.org/z/T1ncMe9Pa) when you do `a.GetAutoRefJ() = 7;` (after `const A a{5};`), mutating the `const` object despite its constness, without even a warning, but I'm guessing it's only because the level of analysis the compiler does doesn't see the problem, and it happens to recheck the values on the before and after print directly, rather than caching like it could. – ShadowRanger Jan 25 '23 at 23:45
  • @ShadowRanger Casting away `const` is not UB. It's trying to modify it afterwards that's UB – NathanOliver Jan 25 '23 at 23:56
  • 1
    @ShadowRanger fun observation. Here's an example where it goes wrong (in two different ways): https://godbolt.org/z/PqhdKdKTd – Chronial Jan 26 '23 at 00:13
2

As NathanOliver explained, the const is applied to the reference itself, not the referenced type.

This might seem confusing, and it might help to remember that reference are mostly just "convenient pointers". If you use a pointer instead, things become more obvious:

struct A {
    int i;
    int& j = i;
    int* k = &i;

    decltype(auto) GetDecltypeAutoJ() const { return j; }
    decltype(auto) GetDecltypeAutoK() const { return *k; }
};

In GetDecltypeAutoK, the type of this->k is int* const, i.e. not int const*. Dereferencing an int* const gives you an int.

It's the same in GetDecltypeAutoJ: the type of this->j is int& const which is identical to int& since references are always constant.

Chronial
  • 66,706
  • 14
  • 93
  • 99