The Darkest Library

[RU] Сборка со стандартом С++23

И какой же русский не любит быстрой езды?

“Мертвые души”, Гоголь, 1842

Уровень материала: смузи / новичок

Статья поддерживает только Ubuntu 20.04.
Поддержка Mac, Windows и Visual Studio появится чуть позже.
Английская версия появится сразу после этого.

Мотивация

Какой же разработчик не любит свежих стандартов?

Но чтобы собирать С+23 нужно пердолить CMake и ставить новые компиляторы. Я немножко превозмог и оформил это в виде статьи.

Писать на свежих стандартах – клёво, приятно и полезно. Я глубоко убеждён, что при малейшей практической возможности нужно обновляться до свежих версий чего угодно. К сожалению, не всегда это просто и дёшево. Эта статья должна немножко помочь в настройке среды для написания своего первого хэлловорлда на С++23.

Начало

Вначале надо выбрать лабораторную мышь.
В интернете есть чудесные примеры кода, типа как у Корентина из Комитета по стандартизации C++ (WG21):

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

Попробуем умножить числа фибоначчи на их порядковый номер. Нет, я не знаю, зачем это может быть нужно, но вы можете оценить бессмысленность и беспощаднось выбранного способа:

int main() {
    const int max_fib_index = 10;
    std::array<int, max_fib_index> indexes{};
    std::ranges::generate(indexes, [n = 1] mutable { return n++; } );

    auto next_fib =[i = 0, j = 1] mutable {
        i = std::exchange(j, i+j);
        return i;
    };

    for (auto index : indexes) {
        std::cout << next_fib() * index << '\n';
    }

    return 0;
}

Сохраним это в файле main.cpp.

После запуска такой программы, ожидается выхлоп вот такого вида:

Опишем проект в файле CMakeLists.txt, который положим рядом:

cmake_minimum_required(VERSION 3.20)
project(cpptest VERSION 1.0)

message("CMake version: ${CMAKE_VERSION}")
message("Compiler ID: ${CMAKE_CXX_COMPILER_ID}")

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_executable(main main.cpp)
  • CMAKE_CXX_COMPILER_ID устанавливается только после этапа project, до этого там пусто!
  • Обязательно установить CMAKE_CXX_STANDARD_REQUIRED, потому что иначе версия “соскоьзнёт” на предыдущую, как описано вот здесь.

Если у вас не установлены cmake и clang, попробуйте сделать это прямо сейчас. На Ubuntu это делается через apt-get install clang cmake, на других операционках сами разберетесь.

Теперь попробуем запустить это:

/usr/bin/cmake --build /home/olegchir/git/cpptest/cmake-build-debug --target main
CMake version: 3.20
-- Configuring done
CMake Error in CMakeLists.txt:
  Target "main" requires the language dialect "CXX23" (with compiler
  extensions), but CMake does not know the compile flags to use to enable it.

В документации на CMake нам обещали, что значение “C++23” заработает из коробки в версии CMake 3.20. Очевидно, этого не произошло.

Какая-то жопа. Давайте разбираться.

Правильные флаги для C++23

Вручную пропишем флаги для разных компиляторов:

cmake_minimum_required(VERSION 3.22)
project(cpptest VERSION 1.0)

message("CMake version: ${CMAKE_VERSION}")
message("Compiler ID: ${CMAKE_CXX_COMPILER_ID}")

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR
    "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
    set(CMAKE_CXX23_STANDARD_COMPILE_OPTION "-std=c++2b")
    set(CMAKE_CXX23_EXTENSION_COMPILE_OPTION "-std=c++2b")
endif()

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    set(CMAKE_CXX23_STANDARD_COMPILE_OPTION "-std=c++23")
    set(CMAKE_CXX23_EXTENSION_COMPILE_OPTION "-std=gnu++23")
endif()

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_executable(main main.cpp)

Возможные варианты CMAKE_CXX_COMPILER_ID описаны здесь.

Кстати, в свежих версиях CMake, можно выбрать между clang-cl и обычным фронтендом. Но это вы уж сами развлекайтесь, тут только идея.

if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
    # clang-cl
  elseif (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
    # regular frontend
  endif()
endif()

От того, что вы всё это вписали, пример не заработает.
Потому что нужно обновить компиляторы и CMake.

Обновление CMake

На той машине, на которой происходит сборка, нужно установить свежий CMake, не ниже версии 3.20.

Если вы используете JetBrains Gateway для Remote Development, то устанавливать его нужно на удалённой машине, к которой вы подключаетесь.

Ubuntu 20.04 Focal

Следуем официальному гайду для APT-репозиториев:

sudo apt-get update
sudo apt-get install gpg apt-transport-https wget

echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main' | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null
sudo apt-get update

sudo rm /usr/share/keyrings/kitware-archive-keyring.gpg
sudo apt-get install kitware-archive-keyring

echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal-rc main' | sudo tee -a /etc/apt/sources.list.d/kitware.list >/dev/null
sudo apt-get update

IDE

Если вы используете IDE от Jetbrains, то нужно пройти в настройки (Preferences -> Build, Execution, Deployment -> Toolchains ) и поменять в вашем текщуем тулчейне поле CMake c built-in (значние по умолчанию) на /usr/bin/cmake.

Скорей всего, там же появится надпись типа: “Version 3.22.1 unsupported; supported versions are 2.8.11-3.21.x”. Звучит страшно, но работать будет даже несмотря на это предупреждение.

И не забудьте применить этот тулчейн на вкладке Preferences -> Build, Execution, Deployment -> CMake.

Если вы работаете в консоли, то оно само появится в PATH.

Выбор версии компилятора и CMake

JetBrains IDE

Если вы пользуетесь CMake из IDE от JetBrains (CLion, CLion + Gateway Remote Development, Rider), то компилятор можно выбрать прямо в графическом интерфейсе.

Зайдите в настройки: Preferences -> Build, Execution, Deployment -> Toolchains и создайте новый тулчейн.

Можно заполнить поля C Compiler (/usr/bin/gcc) и C++ Compiler (/usr/bin/g++). Если их оставить пустыми, то подхватятся те настройки, которые используются в консоли (или в консоли удалённой машины, в случае если вы заускаете CLion через Gateway). А там они, в свою очередь, появляются через механизмы вроде update-alternatives (если ваша операционная система – Linux).

И не забудьте применить этот тулчейн на вкладке Preferences -> Build, Execution, Deployment -> CMake.

Командная строка

Если вы пользуетесь CMake из командной строки, то выбрать компилятор можно так:

cmake -G Ninja -D CMAKE_C_COMPILER=gcc-11 -D CMAKE_CXX_COMPILER=g++-11 /home/olegchir/git/cpptest/cmake-build-debug

Или вот так:

cmake -D CMAKE_C_COMPILER=/usr/bin/gcc -D CMAKE_CXX_COMPILER=/usr/bin/g++ .

Я не фанат командной строки, поэтому напишу ещё парочку возможностей.

Прямо в файле

Неплохо иметь возможность захардкодить компилятор прямо в CMakeLists.

Если использовать сплошь экспериментальные фичи, то всё равно код будет очень зависим от компилятора. Рекомендую выбрать в качестве компилятора Clang, он есть на всех платформах и поэтому более универсален.

Поменяйте немного начало файла CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(cpptest VERSION 1.0)

set( USE_GCC True )

if(USE_GCC)
    message("Using GNU Compiler")
    set( COMPILER_BIN /usr/bin )
    set( CMAKE_CXX_COMPILER_ID GNU )
    set( CMAKE_C_COMPILER ${COMPILER_BIN}/gcc CACHE PATH "gcc" FORCE )
    set( CMAKE_CXX_COMPILER ${COMPILER_BIN}/g++ CACHE PATH "g++" FORCE )
    enable_language( C CXX )
endif()

if(USE_CLANG)
    message("Using Clang Compiler")
    set( COMPILER_BIN /usr/bin )
    set( CMAKE_CXX_COMPILER_ID Clang )
    set( CMAKE_C_COMPILER ${COMPILER_BIN}/clang CACHE PATH "clang" FORCE )
    set( CMAKE_CXX_COMPILER ${COMPILER_BIN}/clang++ CACHE PATH "clang++" FORCE )
    enable_language( C CXX )
endif()

message("CMake version: ${CMAKE_VERSION}")
message("Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
message("C Compiler: ${CMAKE_C_COMPILER}")
message("C++ Compiler: ${CMAKE_CXX_COMPILER}")

# ...

В отдельном файле

Если вы прямо педант, то всё это можно заменить на отдельные файлики:

set( CMAKE_SYSTEM_NAME Linux )

set( COMPILER_BIN /usr/bin )
set( CMAKE_CXX_COMPILER_ID Clang )
set( CMAKE_C_COMPILER ${COMPILER_BIN}/clang CACHE PATH "clang" FORCE )
set( CMAKE_CXX_COMPILER ${COMPILER_BIN}/clang++ CACHE PATH "clang++" FORCE )

Занчения CMAKE_SYSTEM_NAME перечислены здесь, это тупо выхлоп uname -a.

Сохраним его в файл типа linux.cmake и положим рядом c исходниками.

Применить такой файлик можно вот так:

cmake -D CMAKE_TOOLCHAIN_FILE=./linux.cmake --build /home/olegchir/git/cpptest/cmake-build-debug --target main

Проблемы

Если при генерации проекта появляется ошибка вида: “CMake was unable to find a build program corresponding to “Ninja””, то нужно установить на компьютер Ninja. Для Ubuntu 20.04 это строка вида “apt-get install ninja-build”. Для других операционных систем сами догадаетесь.

Если у вас старая версия компилятора, то при попытке собрать проект из CMakeLists.txt, вы получите следуюущую ошибку:

Problems were encountered while collecting compiler information:
	error: invalid value 'c++2b' in '-std=c++2b'

Это если у вас выбран Clang, а для GCC она будет такой:

Problems were encountered while collecting compiler information:
	error: invalid value 'gnu++23' in '-std=gnu++23'

Если собрать такой проект, то мы при компиляции увидим соответствующую проблему:

/usr/bin/clang++   -g -std=c++2b -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /home/olegchir/git/cpptest/main.cpp
error: invalid value 'c++2b' in '-std=c++2b'
note: use 'c++98' or 'c++03' for 'ISO C++ 1998 with amendments' standard
note: use 'gnu++98' or 'gnu++03' for 'ISO C++ 1998 with amendments and GNU extensions' standard
note: use 'c++11' for 'ISO C++ 2011 with amendments' standard
note: use 'gnu++11' for 'ISO C++ 2011 with amendments and GNU extensions' standard
note: use 'c++14' for 'ISO C++ 2014 with amendments' standard
note: use 'gnu++14' for 'ISO C++ 2014 with amendments and GNU extensions' standard
note: use 'c++17' for 'ISO C++ 2017 with amendments' standard
note: use 'gnu++17' for 'ISO C++ 2017 with amendments and GNU extensions' standard
note: use 'c++20' for 'ISO C++ 2020 DIS' standard
note: use 'gnu++20' for 'ISO C++ 2020 DIS with GNU extensions' standard
ninja: build stopped: subcommand failed.

Видите, в списке совершенно нет C++23 или C++2b.

Это решается установкой свежей версии компилятора.

Сборочный скрипт

Если вы работаете на Linux в командной строке, то самый простой скрипт сборки выглядит вот так:

#!/bin/bash

export CURR_TARGET_DIR=/home/olegchir/git/cpptest/cmake-build-debug
export CURR_PROJECT_DIR=/home/olegchir/git/cpptest

cmake --build $CURR_TARGET_DIR --target clean
/usr/bin/cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja -G Ninja -B$CURR_TARGET_DIR $CURR_PROJECT_DIR
/usr/bin/cmake --build $CURR_TARGET_DIR --target main
$CURR_TARGET_DIR/main

Параметры билда надо подкорректировать в соответствии со способом, который вы выбрали выше.

Установка свежей версии GCC

Ubuntu 20.04

sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install gcc-11 g++-11
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 60 --slave /usr/bin/g++ g++ /usr/bin/g++-11

Если у вас превью-версия Ubuntu 22.04 Jammy Jellyfish, то можете там поменять цифру на 12.

Выберите нужную версию GCC:

sudo update-alternatives --config gcc

С высокой вероятностью у вас она и так установлена только одна, и вам напишут: “There is only one alternative in link group gcc (providing /usr/bin/gcc): /usr/bin/gcc-11”. Тогда ничего выбирать не надо. Иначе прожмите нужную цифру.

Проверьте, что установилось нужное:

g++ --version
g++ (Ubuntu 11.1.0-1ubuntu1~20.04) 11.1.0
Copyright (C) 2021 Free Software Foundation, Inc.

Теперь выберите версию компилятора:

sudo update-alternatives --config c++

Нам нужно выбрать нижний g++ в manual mode.

Поставьте в CMakeLists.txt set( USE_GCC True ) и можно собирать проект. Должен собраться без ошибок.

Установка свежей версии Clang

Ubuntu 20.04

Переключимся назад на Clang: sudo update-alternatives --config c++.

Вот эта версия у меня идёт вместе с дистибутивом Ubuntu:

clang --version

clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

Устанавливаем свежую версию (достаточно свежая весия уже лежит в Universe):

sudo apt-get update
sudo apt-get install clang-12
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-12 60 --slave /usr/bin/clang++ clang++ /usr/bin/clang++-12

Не забудьте выбрать нужную версию Clang (впрочем, она у вас там может быть всего одна):

sudo update-alternatives --config clang

Можно пойти ещё дальше, и установить clang-14! Это уже сторонний репозиторий.

# old-stable key
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
# stable key
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
# development key
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -


sudo add-apt-repository 'deb [arch=amd64] http://apt.llvm.org/focal/ llvm-toolchain-focal main'
sudo add-apt-repository -s 'deb [arch=amd64] http://apt.llvm.org/focal/ llvm-toolchain-focal main'
sudo apt-get update
sudo apt-get install clang-14 lldb-14 lld-14
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-14 61 --slave /usr/bin/clang++ clang++ /usr/bin/clang++-14

Не забудьте выбрать нужную версию Clang (впрочем, она у вас там может быть всего одна):

sudo update-alternatives --config clang

Выбрать лучше 14 версию в manual mode.

Поставьте в CMakeLists.txt set( USE_CLANG True ) и можно собирать проект. Должен собраться без ошибок.

PROFIT

Теперь всё должно собраться и запуститься. Типичный выхлоп выглядит вот так:

В системе у вас теперь установлены GCC 11 и Clang 12 (или 14).

Вся подсветка в статье сделана с помощью вот этой утилиты с темой Darkula.

Финальный CMakeLists.txt выглядит так:

cmake_minimum_required(VERSION 3.20)
project(cpptest VERSION 1.0)

set( USE_CLANG True )

if(USE_GCC)
    message("Using GNU Compiler")
    set( COMPILER_BIN /usr/bin )
    set( CMAKE_CXX_COMPILER_ID GNU )
    set( CMAKE_C_COMPILER ${COMPILER_BIN}/gcc CACHE PATH "gcc" FORCE )
    set( CMAKE_CXX_COMPILER ${COMPILER_BIN}/g++ CACHE PATH "g++" FORCE )
    enable_language( C CXX )
endif()

if(USE_CLANG)
    message("Using Clang Compiler")
    set( COMPILER_BIN /usr/bin )
    set( CMAKE_CXX_COMPILER_ID Clang )
    set( CMAKE_C_COMPILER ${COMPILER_BIN}/clang CACHE PATH "clang" FORCE )
    set( CMAKE_CXX_COMPILER ${COMPILER_BIN}/clang++ CACHE PATH "clang++" FORCE )
    enable_language( C CXX )
endif()

message("CMake version: ${CMAKE_VERSION}")
message("Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
message("C Compiler: ${CMAKE_C_COMPILER}")
message("C++ Compiler: ${CMAKE_CXX_COMPILER}")

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
    # haven't tried it yet
endif()

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR
    "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
    set(CMAKE_CXX23_STANDARD_COMPILE_OPTION "-std=c++2b")
    set(CMAKE_CXX23_EXTENSION_COMPILE_OPTION "-std=c++2b")
endif()

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    set(CMAKE_CXX23_STANDARD_COMPILE_OPTION "-std=c++23")
    set(CMAKE_CXX23_EXTENSION_COMPILE_OPTION "-std=gnu++23")
endif()

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_executable(main main.cpp)


Я научил вас всему, что знал. Теперь…

olegchir

Links: Facebook | Twitter | Instagram

Indie game developer. All opinions are my own.

Add comment

Follow me (@olegchir)

Don't be shy, get in touch. I love meeting interesting people and making new friends.

Most popular

Most discussed