Как работают std::launder и std::as_const в C++

5e4deb8999bb854322a6a82426217ab8.png

Есть в C++ такие штуки, которые вроде как существуют в стандарте, но некоторые о них даже не задумываются, пока не наткнутся на что‑то совсем странное. Вот, например, std::launder. Что это вообще? Стирка чего‑то грязного в коде (launder)? Или std::as_const — зачем делать объект «немного более константным»?

На днях решил покопаться в этих функциях, потому что, честно говоря, они звучат интересно. Так что сегодня расскажу, что я выяснил, зачем это всё нужно, и главное — как использовать эти штуки правильно.

Зачем нужен std: launder?

Допустим, вы создаете объект с помощью placement new поверх уже существующего объекта. Вроде бы всё работает. Но где‑то глубоко в недрах кода зреет коварное неопределённое поведение (UB, как его ласково называют на Stack Overflow). Особенно если у старого объекта были const‑члены или ссылки.

Вот тут хорошо зайдет std::launder. Эта функция гарантирует, что доступ к новому объекту через старый указатель не приведёт к катастрофе.

#include 
#include 

struct MyClass {
    const int value;
    MyClass(int v) : value(v) {}
};

int main() {
    alignas(MyClass) char buffer[sizeof(MyClass)];
    new(buffer) MyClass(42);

    MyClass* p = reinterpret_cast(buffer);
    std::cout << p->value << std::endl; // Может привести к неопределённому поведению

    p = std::launder(reinterpret_cast(buffer));
    std::cout << p->value << std::endl; // Всё ок

    return 0;
}

Вот что тут происходит: мы создаём объект MyClass в заранее выделенном буфере. Но если попытаться обратиться к его члену value через указатель p, полученный через reinterpret_cast, может произойти что угодно. Почему? Потому что C++ так решил. А вот std::launder убирает этот фокус и делает всё нормально.

std::launder возвращает указатель на объект, находящийся по тому же адресу, что и указатель p, но при этом гарантирует, что этот объект — «новый». Суть простая: он говорит компилятору, мол, «всё под контролем, можно доверять».

Если формально:

  • p указывает на адрес A в памяти.

  • По адресу A лежит объект x.

  • x находится в пределах времени своей жизни.

  • Тип x совпадает с T (игнорируя const и прочее).

  • Тогда std::launder(p) возвращает указатель на x. Всё просто, если закрыть глаза на тонкости.

Когда использовать std: launder?

Вот типичные ситуации:

  • Placement new. Вы создали новый объект поверх старого, и нужно к нему нормально обращаться.

  • Вы хотите перепривязать указатель к новому объекту, созданному в той же области памяти.

А что с std: as_const?

Это простая, но полезная штука. std::as_const — это функция для ленивых (или предусмотрительных). Она берёт объект и превращает его в const, не изменяя сам объект.

#include 
#include 

void print(const int& value) {
    std::cout << value << std::endl;
}

int main() {
    int x = 42;
    print(std::as_const(x)); // Передаём x как const int&
    return 0;
}

Тут std::as_const(x) берёт переменную x и превращает её в const int&, чтобы мы могли вызвать print.

Как это работает?

Очень просто. Определение функции выглядит вот так:

template< class T >
constexpr std::add_const_t& as_const(T& t) noexcept {
    return t;
}

То есть берём неконстантную ссылку на объект t и возвращаем его как константную ссылку. Всё.

Когда использовать

Используйте, когда:

  • Нужно вызвать константную версию метода.

  • Надо передать объект в функцию, которая ожидает const-ссылку, но сам объект менять не хочется.

Итоги

std::launder и std::as_const — это такие маленькие, но мощные инструменты. Если вам есть чем поделиться на эту тему, пишите в комментарии, интересно послушать!

По C++ в Otus пройдут следующие открытые уроки, записывайтесь, если интересно:

© Habrahabr.ru