75 Advanced The Hundred
The Parentheses That Changed the Type
a getter with no & in sight
#include <iostream>
#include <type_traits>
struct Settings {
int volume = 5;
};
decltype(auto) volume(Settings& s) {
return (s.volume); // extra parentheses never hurt
}
int main() {
Settings s;
volume(s) = 11;
std::cout << "s.volume = " << s.volume << "\n";
// what decltype saw, both ways:
static_assert(std::is_same_v<decltype(s.volume), int>);
static_assert(std::is_same_v<decltype((s.volume)), int&>);
}
Run it. Does assigning to that call even compile — and what does it print?
Answer
It compiles, and prints s.volume = 11. volume returns int& — nothing in the
signature says &, the parentheses said it.
Why¶
decltype answers two different questions. Handed a bare name — an id-expression or a
member access — it reports the declared type of that entity, so decltype(s.volume) is
int. Handed any other expression it reports the type plus the value category, tacking
on & for lvalues, so decltype((s.volume)) is int&. A decltype(auto) return type
just runs decltype over the returned expression, so return (s.volume); hands back a
reference where return s.volume; would hand back a copy.
Handy for a mutable accessor, fatal for a local: compile with -DSHOW_BUG to add
decltype(auto) defaultVolume() { int v = 5; return (v); }, a reference to a variable that
has already died — undefined behavior, and typical output (GCC 11 on x86-64) is the
first line followed by a segmentation fault, at every -O level. GCC catches that one
unaided (-Wreturn-local-addr fires even without -Wall); the reference volume quietly
hands out draws nothing.
The fix¶
int& volume(Settings& s) { return s.volume; } // want a reference? say so in the signature
decltype(auto) defaultVolume() {
int v = 5;
return v; // never parenthesize a return under decltype(auto)
}
Takeaway: decltype asks a name for its declared type and an expression for its value
category — the parentheses decide which question you asked.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo — then again with -DSHOW_BUG