Что такое lvalue в C++?

Ответ

lvalue (left-hand value) — это выражение, которое обозначает объект, занимающий идентифицируемое место в памяти (т.е. имеющий адрес). Его время жизни выходит за рамки одного выражения. Ключевое свойство lvalue — к нему можно применить оператор взятия адреса &.

Характеристики и примеры lvalue:

  • Имена переменных (как фундаментальных, так и пользовательских типов).
    int x = 42; // `x` — lvalue
    std::vector<int> vec; // `vec` — lvalue
  • Ссылки всегда являются lvalue.
    int& ref_x = x; // `ref_x` — lvalue (ссылается на `x`)
  • Результат обращения к элементу по индексу или имени, если контейнер возвращает ссылку.
    vec[0] = 10; // `vec[0]` — lvalue (operator[] у std::vector возвращает T&)
  • Разыменованный указатель.
    int* ptr = &x;
    *ptr = 100; // `*ptr` — lvalue
  • Строковые литералы (в C++ они имеют тип const char[N] и хранятся в памяти).
    const char* p = "hello"; // `"hello"` — lvalue
  • Функции. Имя функции — lvalue.

Важное уточнение: lvalue не обязательно может находиться слева от оператора присваивания. Константные lvalue (const int c = 5;) являются неизменяемыми.

Практическое значение: Понимание категорий значений (lvalue, rvalue, xvalue, prvalue, glvalue) критически важно для:

  1. Работы с ссылками: Невозможно инициализировать неконстантную lvalue-ссылку (int&) rvalue.
    int& a = 10; // Ошибка: 10 — rvalue
    const int& b = 10; // OK: const lvalue-ссылку можно биндить к rvalue
  2. Использования семантики перемещения: Правила выбора перегруженных функций (принимающих T& или T&&) основаны на категории передаваемого выражения.
  3. Оптимизаций: Компилятор может применять разные оптимизации в зависимости от того, является ли выражение lvalue.

Ответ 18+ 🔞

Э, слушай, тут про lvalue надо поговорить, а то народ путается, как мартышлюшка с гранатой. Представь себе переменную — это как твоя собственная квартира. У неё есть конкретный адрес, ты можешь туда прийти, поставить диван или выкинуть старый хлам. Вот это и есть lvalue — штука, у которой есть адрес в памяти, и её можно потрогать оператором & (взять адрес).

Короче, кто эти lvalue:

  • Имена переменных — это прям классика. int x = 42;x это lvalue, его адрес можно спросить: &x.
  • Ссылки — они всегда lvalue. Ссылка — это просто погоняло, кличка для уже существующей переменной. int& ref = x;ref это lvalue.
  • Результат разыменования указателя. Указатель — это бумажка с адресом квартиры. А *ptr — это когда ты по этому адресу пришёл и в дверь пнул. Сама дверь (объект) — lvalue.
  • Обращение к элементу массива или вектора, если он возвращает ссылку. vec[5] = 10;vec[5] это lvalue, потому что std::vector::operator[] возвращает ссылку, ты лезешь прямо в его внутренности.
  • Строковые литералы в кавычках. Да-да, "hello" — это тоже lvalue, массив символов где-то в памяти лежит, хоть и константный. const char* p = "hello"; — тут "hello" и есть lvalue.
  • Имена функций. Да, функция — тоже lvalue, у неё есть адрес.

А теперь главный подвох, ёпта! Многие думают, что lvalue — это то, что обязательно можно поставить слева от =. Это пиздопроебибна! Константа — тоже lvalue, но ей присвоить не получится.

const int mega_const = 100500;
// mega_const — lvalue, у него есть адрес (&mega_const).
// mega_const = 0; // А вот это — ошибка, потому что оно const.

Нахуя это всё надо?

А вот нахуя:

  1. Чтобы не выстрелить себе в ногу со ссылками. Неконстантную lvalue-ссылку (int&) нельзя привязать к временному значению (rvalue). Компилятор наорет.
    int& a = 10; // Ошибка: "Э, сабака сука, куда лезешь? 10 — это rvalue, временщик!"
    const int& b = 10; // А так — ок. Константная ссылка может на всё смотреть.
  2. Чтобы понимать семантику перемещения. Вся эта магия с T&& (rvalue-ссылки) работает именно потому, что компилятор отличает lvalue (у которого есть имя и адрес) от rvalue (которое можно спиздить). От этого зависит, какую версию функции вызовут.
  3. Для оптимизаций. Зная, что выражение — lvalue, компилятор понимает, что объект живёт дольше одной строчки, и может по-другому с ним работать.

Короче, lvalue — это не просто "слева от равно". Это фундаментальная хуйня в языке, про которую надо знать, иначе будешь как тот полупидор, который std::move на константу применяет и удивляется, почему ничего не переместилось.