Skip to content

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

Open in Compiler Explorer ↗ Open SHOW_BUG variant ↗ Quiz this entry