I don't understand the behavior of the following snippet, which is an MRE derived from actual code:
#include <iostream>
#include <type_traits>
// It merely returns its input, which is a reference
// could mutualize code for const and non const member function below
template <typename T>
T& Getter(T& i) {
std::cout << std::boolalpha << "Getter, const int: "
<< std::is_same<T, const int>::value
<< " / int: " << std::is_same<T, int>::value << '\n';
return i;
}
struct S {
int val = -1;
// Read/write access to the data for mutable object
int& Get() {
std::cout << std::boolalpha << "non-const Get, val const int: "
<< std::is_same<decltype(val), const int>::value
<< " / int: " << std::is_same<decltype(val), int>::value
<< '\n';
std::cout << std::boolalpha << "const Get, (val) const int&: "
<< std::is_same<decltype((val)), const int&>::value
<< " / int&: " << std::is_same<decltype((val)), int&>::value
<< '\n';
return Getter(val);
}
// Read-only access to the data for const object
int const& Get() const {
std::cout << std::boolalpha << "const Get, val int const: "
<< std::is_same<decltype(val), const int>::value
<< " / int: " << std::is_same<decltype(val), int>::value
<< '\n';
std::cout << std::boolalpha << "const Get, (val) int const&: "
<< std::is_same<decltype((val)), const int&>::value
<< " / int&: " << std::is_same<decltype((val)), int&>::value
<< '\n';
return Getter(val);
}
};
int main() {
std::cout << "---------------------\nconst\n";
S const ks;
std::cout << ks.Get() << '\n';
}
The output is as follows:
---------------------
const
const Get, val const int: false / int: true
const Get, (val) const int&: true / int&: false
Getter, const int: true / int: false
ks is const thus I'm calling S::Get() const: OK
Inside S::Get() const decltype(val) is int but decltype((val)) is const int&: I find it peculiar.
This has been addressed in:
- Type of member variables in a const member function
- C++11 why the type of 'decltype(x)' and 'decltype((x))' are different?
- Result of decltype in const methods
This behavior is clearly defined in the standard (see the human-friendly version, especially 2-b)).
But I don't get the rationale behind this.
Getter is called on val, passed by reference, but T is deduced as const int, as if the argument was a reference to const lvalue (const int &).
I would have expected that it would be decltype(val) which will the "input" of the template argument deduction.
In most situations, it seems that the difference is invisible, but when dealing with constness it's becoming critical, as in the provided snippet.
What detail am I missing? When passing an expression (possibly a single variable) to a function, what is the type of the expression "seen" by the function (which will impact template argument deduction but, also, possibly, overload resolution)?
It's obviously a basic question, but these subtleties nether occurred to me so far. The linked posts above are revolving about it, but I'm still missing what is actually happening at the function call.
You're missing that when
valis passed as a function argument in the function callGetter(val)is an expression with a value category lvalue with typeconst int. Note also that in the callGetter(val)the expressionvalhas typeconst intbecause we're inside a const member function.This is why
Tis deduced asconst int.Here is a contrived example to make this clear: