Стандарты языков C и C++ и их компиляторы стремительно развиваются, но далеко не все новинки становятся нам доступны сразу.
Эту статью я решил подготовить по случаю недавнего (25 апреля 2025) релиза компилятора GCC 15.1.0. В нем стали доступны для использования некоторые фичи из C++26.
К сожалению, текущая версия Ubuntu (24.04.2 LTS
) изначально содержит достаточно старый GCC 13.3.0. Чтобы использовать что-то поновее, нужен "ручной" подход.
Дальнейший наш план таков. Мы установим новейшую версию GCC из исходников, настроим ее работу в системе по умолчанию и на пробу скомпилируем С++ код, использующий элементы стандарта C++26.
1. Проводим общее обновление системы
Запускаем в терминале:
sudo apt update && sudo apt upgrade -y
Этой командой мы, действуя как администратор (sudo
), даем знать системе о последних доступных через пакетный менеджер версиях компонентов (apt update
).
Затем, если все прошло ок (&&
), устанавливаем доступные обновления apt upgrade
, заранее на это соглашаясь (-y
).
2. Проверяем нашу версию GCC
Нет смысла продолжать, если у нас уже установлена последняя версия компилятора, правда?
sudo g++ --version && g++ --version
Этой командой мы узнаем, какая версия С++ компилятора используется у нас на обычном пользовательском аккаунте и на аккаунте администратора.
В ответ, как правило, терминал выдаст два идентичных ответа, где будет указано что-то вроде g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
.
Если эта версия компилятора слишком стара для нас, идем дальше...
3. Устанавливаем необходимые зависимости
sudo apt install -y build-essential git make gawk flex bison libgmp-dev libmpfr-dev libmpc-dev python3 binutils perl libisl-dev libzstd-dev tar gzip bzip2
Это набор программ, который нам потребуется при сборке новейшего GCC на нашей системе.
Некоторые из этих компонентов уже предустановлены в Ubuntu. К примеру, build-essential
, среди прочего, проверит и гарантирует наличие изначально присутствующих в системе gcc, g++, make
.
export CONFIG_SHELL=/bin/bash
Данная команда устанавливает переменную среды CONFIG_SHELL
в состояние /bin/bash
. В результате скрипты, которые будут запускаться при последующей сборке GCC, будут обрабатываться именно через требуемый самими скриптами bash
, а не через какую-то иную оболочку.
4. Скачиваем компоненты новейшего GCC
mkdir ~/gcc-15
cd ~/gcc-15
git clone https://gcc.gnu.org/git/gcc.git gcc-15-source
cd gcc-15-source
git checkout releases/gcc-15.1.0
./contrib/download_prerequisites
На этой стадии мы создаем папки (gcc-15
, gcc-15-source
) и закачиваем туда исходники компилятора из репозитория (https://gcc.gnu.org/git/gcc.git
) с необходимыми зависимостями (./contrib/download_prerequisites
).
Мы также переключаем состояние скачанного репозитория на нужную нам версию компилятора. На конец апреля 2025 - это версия 15.1.0
.
Команда git checkout releases/gcc-15.1.0
использует теги, которые, по сути, простые указатели на определенные коммиты со стабильными версиями исходников.
В дальнейшем выйдут новые версии. С полным списком доступных гит-тегов мы можем ознакомиться, введя команду git tag -l
.
5. Настраиваем будущий билд
cd ~/gcc-15
mkdir gcc-15-build
cd gcc-15-build
../gcc-15-source/configure --prefix=/opt/gcc-15 --disable-multilib --enable-languages=c,c++
Далее в директории gcc-15
мы создаем папку сборки gcc-15-build
, в которой будет создан рабочий билд нашего компилятора, и переходим в нее.
Оттуда мы отправляем команду создания Make-файла с правилами, по которым будет проводиться сборка проекта.
--prefix=/opt/gcc-15
определяет, что GCC установится в папку /opt/gcc-15
. Так новая версия будет существовать отдельно от уже установленного у нас GCC старой версии.
--disable-multilib
предполагает, что собрана будет лишь версия компилятора, которая полностью совместима в текущей системой (к примеру, на x64 системе не будет создан x32 компилятор). Такой подход ускорит сборку и исключит возможные ошибки.
В --enable-languages=c,c++
мы перечисляем языки, которые будет поддерживать наш компилятор. GCC, к слову, может еще работать с иными языками, вроде fortran
, go
, d
...
6. Собираем компилятор
make -j$(nproc)
Команда запускает сборку по правилам, которые мы определили в ранее созданном Make-файле.
Опция -j
(полная форма --jobs
) требует проводить компиляцию многопоточно для более быстрой сборки. В целом, эта стадия самая долгая и займет от получаса до нескольких часов, в зависимости от ресурсов машины.
Подстановка $(nproc)
достанет из системы общее число ядер процессора. Соответственно, все они будут задействованы сборке GCC. При желании можно указать конкретное число (make -j8
) или подойти чуть более творчески (make -j$(($(nproc) - 1))
).
7. Инсталлируем и подключаем GCC
Команда sudo make install
запустит инсталляцию собранного компилятора и сделает его готовым к работе.
Между тем, на данный момент запрос g++ --version
выдаст нам номер старой версии GCC, а система в целом будет вести себя так, будто ничего не произошло.
Потому последним шагом нам следует объяснить системе, какой из наличных GCC отныне считается главным.
sudo update-alternatives --install /usr/bin/gcc gcc /opt/gcc-15/bin/gcc 100
sudo update-alternatives --install /usr/bin/g++ g++ /opt/gcc-15/bin/g++ 100
Эти команды зарегистрируют в системе бинарные файлы нового компилятора, расположенные в /opt/gcc-15/bin/
.
Мы используем две команды, потому что g++
это обычный C++ компилятор, работающий по ISO стандартам, тогда как gcc
- это C компилятор с поддержкой C++ и собственными GCC-расширениями.
Параметр 100
означает приоритет. Бинарники с большим приоритетом выбираются по умолчанию, если мы не предусмотрим иного.
sudo update-alternatives --config gcc
sudo update-alternatives --config g++
Каждая из этих команд выведет в консоль нумерованный список зарегистрированных бинарников и предложит выбрать тот, который будет вызываться при обращении к g++
или gcc
.
На этом все. Теперь команда g++ --version
должна возвращать номер той самой последней версии компилятора GCC, которую мы все это время устанавливали.
В дальнейшем, если мы решим вернуться к использованию изначальной "системной" версии, нам помогут те же команды.
Если старый компилятор у нас на тот момент будет не зарегистрирован, нужно будет провести его регистрацию:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 50
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 50
8. Наслаждаемся новыми фичами
GCC 15.1 открыл нам доступ к целому ряду особенностей, запланированных для стандарта C++26. Теперь мы можем на практике ознакомиться с будущими новациями языка.
Для этого создадим отдельную директорию, перейдем туда и сделаем там .cpp
файл.
mkdir gcc_test_folder
cd gcc_test_folder
touch test26.cpp
Запишем в файл следующий код:
#include <iostream>
#include <format>
// ===================================================
// Feature 1: Pack Indexing
// ===================================================
template<typename... Types>
void PrintFirstArg(Types... Args)
{
std::cout << std::format("{}\n\n", Args...[0]);
}
void TestPackIndexing()
{
puts("=== Feature 1: Pack Indexing ===");
PrintFirstArg('a', "two", 33);
}
// ===================================================
// Feature 2: Attributes for Structured Bindings
// ===================================================
void TestAttributesForStructuredBindings()
{
puts("=== Feature 2: Attributes for Structured Bindings ===");
struct StructS1 { int A = 10, B = 20; } S;
auto [A [[maybe_unused]], B [[deprecated]]] = S;
std::cout << std::format("A = {}, B = {}\n\n", A, B);
}
// ===================================================
// Feature 3: Variadic Friends
// ===================================================
template<typename... Ts>
class Passkey
{
friend Ts...;
Passkey() { }
};
class ClassA;
class ClassB;
struct Widget
{
void Secret(Passkey<ClassA, ClassB>) const
{
puts("Secret accessed!");
}
};
struct ClassA
{
void Access(const Widget& W) const { W.Secret({}); }
};
struct ClassB
{
void Access(const Widget& W) const { W.Secret({}); }
};
struct ClassD
{
// Compilation error: ClassD is not a friend
// void Access(Widget& W) { W.Secret({}); }
};
void TestVariadicFriends()
{
puts("=== Feature 3: Variadic Friends ===");
Widget W;
ClassA A, B;
A.Access(W);
B.Access(W);
puts("");
}
// ===================================================
// Feature 4: Constexpr Placement New
// ===================================================
constexpr int ConstexprPlacementNew()
{
std::allocator<int> Allocator;
int* P = Allocator.allocate(16);
new (P) int(33);
int Value = *P;
Allocator.deallocate(P, 16);
return Value;
}
void TestConstexprPlacementNew()
{
puts("=== Feature 4: Constexpr Placement New ===");
constexpr int Result = ConstexprPlacementNew();
std::cout << std::format("Value constructed with placement new: {}\n\n", Result);
}
// ===================================================
// Feature 5: Structured Binding as Condition
// ===================================================
struct StructS2
{
int A, B;
explicit operator bool() const noexcept
{
return A != B;
}
};
void Use(int A, int B)
{
std::cout << std::format("Using A = {}, B = {}\n", A, B);
}
void TestStructuredBindingAsCondition()
{
puts("=== Feature 5: Structured Binding as Condition ===");
StructS2 S{10, 20};
if (auto [A, B] = S)
{
Use(A, B);
}
puts("");
}
// ===================================================
// Feature 6: Deleted Function with Reason
// ===================================================
void OldFunction(char*) = delete("unsafe, use NewFunction instead");
void NewFunction(char*) { }
void TestDeletedFunctionWithReason()
{
// error: use of deleted function ‘void OldFunction(char*)’: unsafe, use NewFunction instead
// OldFunction("test");
}
// ===================================================
// Feature 7: Deleting Pointer to Incomplete Type
// ===================================================
struct IncompleteType;
void TestDeletingIncompleteType(IncompleteType* Pointer)
{
// delete Pointer; // Compilation error in C++26
}
// ===================================================
// Feature 8: Oxford Variadic Comma
// ===================================================
void DeprecatedFunction(auto......) { } // omission of ‘,’ before varargs ‘...’ is deprecated in C++26
void OkayFunction(auto..., ...) { }
void DeprecatedG(int...) { } // Deprecated in C++26
void OkayG(int, ...) { }
void DeprecatedH(int X...) { } // Deprecated in C++26
void OkayH(int X, ...) { }
// ===================================================
// Main Function
// ===================================================
int main()
{
TestPackIndexing();
TestAttributesForStructuredBindings();
TestVariadicFriends();
TestConstexprPlacementNew();
TestStructuredBindingAsCondition();
TestDeletedFunctionWithReason();
TestDeletingIncompleteType(nullptr);
}
Этот код содержит все основные поддерживаемые GCC 15.1.0 фичи стандарта С++26, которые мы можем испытать в рамках одного компилируемого кода.
Далее, пребывая в той же испытательной директории, проведем компиляцию нашего файла без GNU расширений, но с поддержкой C++26 (-std=c++2c
или -std=c++26
) и запустим полученную в результате программу (./test26
):
g++ -std=c++26 test26.cpp -o test26
./test26
Если файл успешно скомпилировался, в процессе выдав нам несколько запланированных предупреждений, у нас все получилось.