#099 – Why we don’t return non-const static locals by reference

Recap on Return by reference

Returning by reference tells the compiler to avoid creating a duplicate of the type we are returning. Rather, we return a reference to the object being returned.

In this example, we add const before the function type. For example:

#include <iostream>
#include <string>

const std::string& defaultWifiName()
{
    static const std::string wifiName { "Home WiFi" };
    return wifiName;
}

int main()
{
    std::cout << "Connecting to: " << defaultWifiName() << '\\n';
}

// Terminal output:
Connecting to: Home WiFi

The object being returned by reference must persist after the function executes

The example above only compiled since the return type was const. This allows the local variable to outlive the function. Since we use a const lvalue reference, it binds to the C-style literal, an rvalue. This gives allows it to live beyond the function call.

When we remove the const from the return type, the compiler doesn’t like it

/tmp/nLlwf954wn/main.cpp:7:12: error: binding reference of type 'std::string&'
{aka 'std::__cxx11::basic_string<char>&'} to 'const std::string'
{aka 'const std::__cxx11::basic_string<char>'} discards qualifiers
    7 |     return s_programName;

The lifetime rule

This is not safe:

#include <string>

const std::string& badProgramName(){
    std::string name { "Telemetry Viewer" };
    return name;
}

The variable name is a normal local variable. It is destroyed when the function returns. A dangling reference has been created.

NOTE: Objects returned by reference must live beyond the scope of the function returning the reference, or a dangling reference will result. Never return a (non-static) local variable or temporary by reference.

static fixes lifetime, not mutability

static local variable does live long enough to be returned by reference.

But lifetime is not the only issue.

Consider this function:

#include <iostream>
#include <string>

const std::string& nextTicketLabel(){
    static int nextId { 100 };
    static std::string label {};

    label = "TICKET-" + std::to_string(nextId++);

    return label;
}

int main(){
    const std::string& first { nextTicketLabel() };
    const std::string& second { nextTicketLabel() };

    std::cout << first << '\\n';
    std::cout << second << '\\n';
}

// Terminal output:
TICKET-101
TICKET-101

At first glance, you might expect:

TICKET-100
TICKET-101

But both references refer to the same static object:

static std::string label {};

The first call returns a reference to label when it contains "TICKET-100".

The second call changes the same label object to "TICKET-101".

Since first and second are both references to the same object, they both print the latest value.

To fix this, drop the reference from first and second. Now each caller receives a duplicate.

TICKET-100
TICKET-101

Thus, avoid returning references to non-const local static variables.