На недавней встрече комитет C++ «прорвало», и в черновую версию C++23 добавили:
std::mdspan
std::flat_map
std::flat_set
- freestanding
std::print("Hello {}", "world")
- форматированный вывод ranges
-
constexpr
дляbitset
,to_chars/from_chars
std::string::substr() &&
import std;
std::start_lifetime_as
static operator()
[[assume(x > 0)]];
- 16- и 128-битные float
std::generator
- и очень много другого
std::mdspan
После того как на прошлой встрече приняли многомерный
operator[]
, реализация std::mdspan
упростилась на порядок. И вот результат, теперь есть невладеющий тип многомерного массива: using Extents = std::extents<std::size_t, 42, 32, 64>;
double buffer[
Extents::static_extent(0)
* Extents::static_extent(1)
* Extents::static_extent(2)
];
std::mdspan<double, Extents> A{ buffer };
assert( 3 == A.rank() );
assert( 42 == A.extent(0) );
assert( 32 == A.extent(1) );
assert( 64 == A.extent(2) );
assert( A.size() == A.extent(0) * A.extent(1) * A.extent(2) );
assert( &A(0,0,0) == buffer );
Из коробки предусмотрена возможность работы с другими языками программирования. Так,
std::mdspan
третьим шаблонным параметром принимает класс-layout и есть несколько предопределённых классов:-
std::layout_right
— стиль расположения для C или C++, строки идут нулевым индексом, -
std::layout_left
— стиль расположения для Фортрана или Матлаба, колонки идут нулевым индексом.
Все подробности доступны в документе P0009. Авторы обещали в ближайшее время предоставить большой набор примеров по использованию нового
std::mdspan
.std::flat_map и std::flat_set
Замечательные контейнеры flat_* из Boost теперь доступны в стандарте C++. Основная фишка этих контейнеров — очень быстрая работа на небольших объёмах данных. Под капотом «плоские» контейнеры хранят данные в отсортированном массиве, что значительно уменьшает количество динамических аллокаций и улучшает локальность данных. Несмотря на сложность поиска O(log N) и, в худшем случае, сложность вставки O(N), плоские контейнеры обгоняют
std::unordered_map
по скорости на небольших объёмах.Правда, в процессе стандартизации решили переделать контейнеры flat_* в адаптеры, чтобы можно было менять нижележащую имплементацию на использование своих контейнеров:
template <std::size_t N>
using MyMap = std::flat_map<
std::string, int, std::less<>,
mylib::stack_vector<std::string, N>, mylib::stack_vector<int, N>
>;
static MyMap<3> kCoolestyMapping = {
{"C", -200},
{"userver", -273},
{"C++", -273},
};
assert( kCoolestyMapping["userver"] == -273 );
const auto& keys = kCoolestyMapping.keys(); // Вдохновлено Python :)
assert( keys.back() == "userver" );
Интересный момент: в отличие от Boost-реализации, в стандарте ключи и значения контейнера лежат в разных контейнерах. Это позволяет ускорить поиски во flat-контейнерах за счёт большей локальности расположения ключей.
Полный интерфейс
std::flat_set
описан в документе P1222, интерфейс std::flat_map
в документе P0429.Freestanding
В стандарте C++ прописана возможность иметь такие реализации стандартной библиотеки, как
hosted
и freestanding
. Реализация hosted
требует поддержки операционной системы и обязана реализовывать все методы и классы из стандартной библиотеки. Freestanding
может работать без ОС, на любой железке и не содержать часть классов и функций.Вот только до недавнего времени не было описания
freestanding
, и разные производители железок предоставляли разные части стандартной библиотеки. Это усложняло портирование кода и подрывало популярность C++ в embedded-среде.Настало время это изменить! В P1642 разметили обязательные для
freestanding
части стандартной библиотеки.std::print
В C++20 внесли методы из популярной библиотеки fmt. Библиотека оказалась настолько удобной и быстрой, что её начали использовать практически везде в коде, в том числе для форматированного вывода:
std::cout << std::format("Hello, {}! You have {} mails", username, email_count);
Но у такого кода есть проблемы:
- возникнут лишние динамические аллокации,
-
std::cout
будет пытаться форматировать уже отформатированную строчку, - нет поддержки Юникода,
- такой код увеличивает размер результирующего бинарного файла,
- он выглядит некрасиво.
Все проблемы победили добавлением методов
std::print
: std::print("Привет, {}! У вас {} писем", username, email_count);
Подробности, бенчмарки, а также возможность использовать c
FILE*
или стримами описаны в документе P2093.Форматированный вывод диапазонов значений
Благодаря P2286,
std::format
(и std::print
) обзавелись возможностью выводить диапазоны значений — вне зависимости от того, сохранены ли они в контейнер или представлены std::ranges::views::*
: std::print("{}", std::vector<int>{1, 2, 3}); // Вывод: [1, 2, 3]
std::print("{}", std::set<int>{1, 2, 3}); // Вывод: {1, 2, 3}
std::print("{}", std::pair{42, 16}); // Вывод: (42, 16)
std::vector v1 = {1, 2};
std::vector v2 = {'a', 'b', 'c'};
auto val = std::format("{}", std::views::zip(v1, v2)); // [(1, 'a'), (2, 'b')]
constexpr
Очень большая радость для разработчиков разных библиотек для парсинга:
std::to_chars/std::from_chars
теперь можно использовать на этапе компиляции для превращения текстового представления целочисленного значения в бинарное. Такая функциональность полезна и при разработке DSL. Мы в Yandex Go планируем со временем начать это использовать для проверок SQL-запросов на этапе компиляции во фреймворке userver.std::bitset
тоже стал constexpr
, так что и с битами теперь можно удобно работать на этапе компиляции.Даниил Гочаров работал над std::bitset P2417 и, вместе с Александром Караевым, над std::to_chars/std::from_chars P2291. Огромное спасибо им за проделанную работу! Обоих ребят можно найти в чатике по C++ pro.cxx и поздравить.
import std;
В стандартную библиотеку добавили первый полноценный модуль. Теперь всю библиотеку можно подключить одной строчкой
import std;
. Время сборки может ускориться в 11 раз (а иногда и в 40 раз!), если вместо заголовочных файлов подключить сразу весь модуль стандартной библиотеки. Бенчмарки есть в P2412.Если вы привыкли смешивать код на C++ с кодом на C и используете C-функции из глобального namesapce, то специально для вас сделали модуль
std.compat
. Импортировав его, вы получите не только всё содержимое стандартной библиотеки, но и все функции из заголовочных файлов C, например ::fopen
и ::isblank
.При этом сам документ P2465 на новые модули получился небольшим.
std::start_lifetime_as
Тимур Думлер и Ричард Смит сделали прекрасный подарок всем разработчикам embedded- и высоконагруженных приложений. Теперь можно делать так, и всё обязано работать:
struct ProtocolHeader {
unsigned char version;
unsigned char msg_type;
unsigned char chunks_count;
};
void ReceiveData(std::span<std::byte> data_from_net) {
if (data_from_net.size() < sizeof(ProtocolHeader)) throw SomeException();
const auto* header = std::start_lifetime_as<ProtocolHeader>(
data_from_net.data()
);
switch (header->msg_type) {
// ...
}
}
Другими словами, без
reinterpret_cast
и неопределённого поведения можно конвертировать разные буферы в структуры и работать с этими структурами без копирования данных. Найти и поздравить Тимура можно всё в том же чатике по C++ pro.cxx, а полюбоваться на сам документ P2590 — здесь.16- и 128-битные float
Стандарт C++ обзавёлся
std::float16_t
, std::bfloat16_t
, std::float128_t
и алиасами для уже существующих чисел с плавающей запятой: std::float32_t
, std::float64_t
.16-битные float полезны при работе с видеокартами и в машинном обучении. Например, можно более эффективно реализовать float16.h в CatBoost. 128-битные float пригодятся для научных вычислений с большими числами.
В документе P1467 описаны макросы для проверки поддержки новых чисел компилятором, и даже есть сравнительная таблица
stdfloat.properties
с описанием размеров мантисс и экспонент в битах.std::generator
Когда в стандарт C++20 принимали корутины, целились в то, что одним из вариантов их использования может быть создание «генераторов». То есть функций, которые помнят своё состояние между вызовами и возвращают новые значения, исходя из этого состояния. В C++23 добавили класс
std::generator
, позволяющий легко создавать свои генераторы:std::generator<int> fib() {
auto a = 0, b = 1;
while (true) {
co_yield std::exchange(a, std::exchange(b, a + b));
}
}
int answer_to_the_universe() {
auto rng = fib() | std::views::drop(6) | std::views::take(3);
return std::ranges::fold_left(std::move(rng), 0, std::plus{});
}
В примере видно, что генераторы хорошо сочетаются с ranges. Помимо этого, как мы рассказывали на февральской встрече РГ21,
std::generator
эффективен и безопасен. Код, который, кажется, порождает висящую ссылку, на самом деле абсолютно валиден и не приводит к неприятностям:std::generator<const std::string&> greeter() {
std::size_t i = 0;
while (true) {
co_await promise::yield_value("hello " + std::to_string(++i)); // Всё OK!
}
}
Примеры, описание внутренней работы и обоснование выбранного интерфейса доступны в документе P2502.
Приятные мелочи
Стандартный класс строки обзавёлся новой перегрузкой метода
substr()
для временных строк: std::string::substr() &&
. Код наподобие такого…std::string StripSchema(std::string url) {
if (url.starts_with("http://")) return std::move(url).substr(5);
if (url.starts_with("https://")) return std::move(url).substr(6);
return url;
}
...теперь отработает без лишних динамических аллокаций. Подробности — в документе P2438.
Благодаря P1169 в ядре языка появилась возможность помечать
operator()
как static
. В стандартной библиотеке подобный приём хорошо подходит для создания CPO для ranges:namespace detail {
struct begin_cpo {
template <typename T>
requires is_array_v<remove_reference_t<T>>
|| member_begin<T> || adl_begin<T>
static auto operator()(T&& val);
};
void begin() = delete; // poison pill
} // namespace detail
namespace ranges {
inline constexpr detail::begin_cpo begin{}; // ranges::begin(container)
} // namespace ranges
Тимур Думлер, помимо
std::start_lifetime_as
, отличился ещё и отличным хинтом для оптимизатора [[assume(x > 0)]]
. Теперь можно давать подсказки компилятору о возможных значениях чисел и других инвариантах. Примеры и бенчмарки P1774 в некоторых кейсах показывают пятикратное сокращение числа ассемблерных инструкций.Прочее
В стандарт также попало множество небольших правок, багфиксов и улучшений. Где-то начали использоваться move-конструкторы вместо конструкторов копирований (P2266). На радость разработчикам драйверов часть операций с volatile больше не является deprecated (P2327 с багфиксом в C++20).
operator<=>
стал меньше ломать старый код (P2468), юникодные символы теперь можно использовать по их имени (P2071), да и вообще все компиляторы обязали поддерживать Юникод (P2295). Добавили новые алгоритмы для ranges (ranges::contains P2302, views::as_rvalue P2446, views::repeat P2474, views::stride P1899 и ranges::fold P2322), std::format_string
с целью проверки на этапе компиляции данных для std::format
(P2508) и #warning
(P2437). ranges научились работать с move-only-типами (P2494). И наконец, добавили std::forward_like
для форварда переменной, основанного на типе другой переменной (P2445).Итоги
Долгое время казалось, что самым значительным нововведением C++23 станет добавление
std::stacktrace
от РГ21 — но на последней встрече добавили множество давно ожидаемых фич. Есть новинки и для embedded-разработчиков, и для людей, занимающихся химией/физикой/математикой/..., и для разработчиков библиотек машинного обучения, и для тех, кто делает высоконагруженные приложения.Теперь, когда фичи C++23 зафиксированы, нам нужна ваша помощь! Если вы видите какие-то проблемы в C++23 или вам что-то сильно мешает в C++ — пишите на stdcpp.ru свои предложения по улучшению языка. Важные вещи и замечания мы закинем комментарием к стандарту, и есть все шансы, что их быстро поправят.
Кстати, мы всегда рады рассказать про новинки C++ и фичи, которые вот-вот окажутся в стандарте (например, про извлечение
std::stacktrace
из исключения или про std::get<1>
для агрегатов).В этот раз встреча рабочей группы 21 по итогам заседания комитета пройдёт 30 июля на конференции C++ Zero Cost Conf. Зарегистрироваться можно здесь: будут приятные сюрпризы и возможность получить ответ на волнующий вас вопрос.
Комментарии (120)
Sixshaman
26.07.2022 11:41+4Жаль, #embed в стандарт не попадёт.
Автор пропозала жалуется, что комитет C++ слишком упрямый. Даже в C уже приняли.
antoshkka Автор
26.07.2022 11:49+2В C++23 просто не успели принять, сконцентрировались на других вещах :(
Но то что приняли в C23 - очень позитивная новость. Значит скоро примут и в C++, ведь комитет старается сохранять совместимость. Ну и когда реализуют в C компиляторах, фича сама "просочится" в C++
G1ROG
26.07.2022 13:09+3Меня, как начинающего разработчика, конечно радует, что язык активно развивается, однако, когнитивная нагрузка от новых стандартов только возрастает. Надеюсь, комитет уделяет достаточно времени и депрекейту легаси там, где это возможно.
antoshkka Автор
26.07.2022 13:21+9В комитете есть несколько людей, которые активно следят за простотой использования языка. Например Nicolai Josuttis преподаёт C++ студентам, и часто приходит в комитет со словами "Вот этот момент студенты не понимают, и я кажется тоже! Давайте вот так переделаем" или "Тут приходится слишком много и долго объяснять, смотрите как можно упростить"
NickSin
26.07.2022 14:44+2У меня так же в вузе. Веду программирование на начальных курсах. Какие-то вещи из нового стандарта студенты не понимают. А преподавать что-то ниже с++17 не хочется + каждый год ты обязан "улучшать" программу образования курса. Так что преподавателя я понимаю.
allcreater
26.07.2022 17:57+2Но, с другой стороны, Вам ведь не требуется запихнуть весь стандарт языка от корки до корки в студентов, достаточно показать подмножество языка, достаточное для эффективного решения большинства задач. Если какая-то штука не вносит значимого улучшения в эффективность, и не может использоваться без детального объяснения принципа работы - может, и не стоит ее тогда показывать?
А насчет улучшения программы, кмк достаточно поглядывать на списки нововведений по стандартам, выбрать несколько любимых и удобных для преподавания, и по возможности писать/обновлять примеры с использованием этих нововведений и стараться соответствовать официальным гайдлайнам. Если при этом не запрещать студентам использовать последний доступный в основных компиляторах стандарт, то едва ли у кого-то повернется язык назвать Ваш курс "несовременным" :)
Извините за скомканность, советы вряд ли универсальные, но хотелось поделиться мыслями.Omrigan
27.07.2022 13:32+2В работе ты имеешь шансы встретить всё многообразие языка, потому что не все в мире подчиняются твоему пониманию эффективного подмножества
allcreater
27.07.2022 14:12Ну так лично я себя и так считаю (относительно) профессиональным программистом-плюсовиком, стандарт почитываю при каждом удобном случае, новые фичи изучаю взахлеб. Обожаю язык и, несмотря на уже больше десяти лет практики, до сих пор узнаю о нем кучу нового :)
Я пишу в контексте преподавания и студентов, которые в любом случае после прослушивания курса будут еще много практиковаться, стажироваться и/или джуниорить - а на первой работе бест практисы перенимаются у старших коллег и программистские навыки растут очень шустро.
holydel
26.07.2022 13:21+3Такое ощущение, что в комитет прорвалось много ребят с геймдева. Очень много вкусных штук для нас.
antoshkka Автор
26.07.2022 13:23+2А расскажите поподробнее, что именно вам пригодится?
holydel
26.07.2022 14:36+916 битные флоаты в стандарте - то, чего реально не хватало.
Флэт контейнеры, constexpr битсеты - очень приятно.
Print, start_lifetime_as - просто приятно.
Kelbon
26.07.2022 14:45+1принт очень интересно выглядит с выводом на экран через свой output iterator ))
NickSin
26.07.2022 14:45+1Интересно бы было примеры с юз-кейсами посмотреть. Просто на словах вам оно понятно, а как оно на практике у вас используется (можно с абстракцией) было бы интересно посмотреть.
8street
27.07.2022 08:53+3Можно кейс применения 16-битных флоатов? Мне пока видится, если только на 16 битном процессоре. И то, у него, наверное, не будет FPU блока.
Torvald3d
27.07.2022 12:14+1Обработка фотографий с высоким динамическим диапазоном (где float32 очень много, float16 - в самый раз). HDR изображения.
Для меня как разработчика графических редакторов введение float16 - самая важная фича. Правда неизвестно как будет реализовано в железе. Теоретически все современные процессоры умеют в fp16, но на сколько медленно будут работать такие числа на цпу их не поддерживающие - вопрос
DistortNeo
27.07.2022 15:10+1Теоретически все современные процессоры умеют в fp16
Они умеют только делать векторное преобразование FP16 <-> FP32 с помощью AVX-команд vcvtph2ps и vcvtph2ps, но не напрямую работать с FP16.
kin4stat
26.07.2022 13:43+6По тому как описывали новый стандарт ранее, казалось, что выйдет самый гнусный и скучный стандарт. А тут сразу и модули, и дополнения к ренжам которых так не хватало, assume, #warning и много еще плюшек
start_lifetime_as особенно порадовал. Только недавно в procxx «воевали» на этот счет =)
sergegers
26.07.2022 14:03+3Проясните, Антон, пару-тройку моментов.
По поводу печати рэнджей. Я Эрику коммитнул макрос, отключающий печать рэнджей бай дефолт, чтобы пользоваться своей реализацией. Можно ли переопределить стандартную реализацию?
Ещё про рэнджи. В реализации Эрика Ниблера есть такие штуки как cursor, adaptor. Их вроде как просто не успели принять в C++ 20. Я переехал на ranges v3 с ranges v2 из boost, но сейчас я не могу пользовать стандартную реализацию из-за кастом вьюс. Попали ли они в C++ 23?
std::format при всей констэкспрешности утерял, кажется, важную опцию, присущую boost::format. На аргумент в boost::format можо было напустить манипуляторы, а на std::format, насколько я понимаю - нет. Это - большой пробел.
По поводу многоразмерных массивов - они конфликтуют с перегрузкой оператора (,) . Как решено это в стандарте и какова судьба библиотек типа boost::phoenix, boost::karma, boost::spirit?
antoshkka Автор
26.07.2022 14:34на std::format, насколько я понимаю - нет
В std::format уже есть предопределённые форматтеры на ширину выведения числа, на точность, на предстваление, на заполненители и т.д.
Если нужно что-то большее, то можно для своих типов можно добавлять любые форматтеры через специализацию std::formatter для своего типа.
Можно ли переопределить стандартную реализацию?
Да, например можно сделать
```
template <typename Range>
struct MyPrinter{ Range r; };```
И специализировать std::formatter<MyPrinter<T>>. Тогда, если надо как-то по особомо отформатировать свой диапазон, то надо будет написать std::print("{:ваши-флаги-форматирования}", MyPrinter{range});
не могу пользовать стандартную реализацию из-за кастом вьюс
Есть способы писать свои вьюихи, но если это делать по всем правилам, получается нетривиально. Со статическим operator() это становится попроще
они конфликтуют с перегрузкой оператора (,)
Использование этого оператора в operator[] было задепрекейчено в C++20, а оператор для многомерных массивов был добавлен в C++23. Так что если есть подобные использования, то предлагается сначала мигрировать на C++20, поправить все использования, после чего уже переезжать на C++23. Решение не самое изящноеб но рабочее.
какова судьба библиотек типа boost::phoenix, boost::karma, boost::spirit
Там вроде не используется оператор запятая в квадратных скобках
sergegers
26.07.2022 15:14-3есть предопределённые форматтеры на ширину выведения числа, на точность, на предстваление, на заполненители и т.д
Я не стал бы кукситься на стандартые форматеры. Естественно, меня интересуют свои.
И специализировать std::formatter<MyPrinter<T>>
Там вроде не используется оператор запятая в квадратных скобках
Иммитация лямбд, например. Кстати, так и не приняли аббривиэйтед лямбды, или даже супер-мега-аббривиэйтед. Поэтому, например, бустовские лямбды не могут переехать на стандартные, даже если бы захотели.
А deduced this не заменяет CRTP внезапно. В общем - говно, сорри. Я к вам неплохо отношусь, но истина дороже.
vt4a2h
26.07.2022 14:15+1Есть ли новости касательно std::expected? Я где-то читал что уже добавлено в 23, но возможно я ошибаюсь.
antoshkka Автор
26.07.2022 14:36+1std::expected добавили в C++23 в феврале. Вот тут мы делились подробностями https://habr.com/ru/company/yandex/blog/649497/
crackedmind
26.07.2022 15:57+1Как минимум в msvc его уже можно использовать с версии VS 2022 17.3 Preview 3
JordanCpp
26.07.2022 14:26+2Планирует ли комитет, добавить в стандарт абстракцию над SSE1,2,3,4 и AVX512 и иже с ними? Что бы не приходилось юзать богопротивные Intrinsic:) При просмотре Intrinsic'ов глаза текут:)
antoshkka Автор
26.07.2022 14:43+2Над этим идёт активная работа, будет выглядеть вот так https://en.cppreference.com/w/cpp/experimental/simd/simd
В GCC уже добавили реализацию, можно пользоваться :)
JordanCpp
26.07.2022 14:47+1Здорово. Загуглил, нашел примеры https://github.com/VcDevel/std-simd
Выглядит хорошо.
PyerK
26.07.2022 15:17Я так понимаю execution::unsequenced_policy ещё ни один компилятор не реализовал в виде simd, кроме заглушек ? Я пробовал годболтить на основных, код ничем не отличался от стандартного расчитанного на c++threadmodel (читаем по элементу за раз, например для строк - по байту за итерацию).
panteleymonov
26.07.2022 14:37+1std::start_lifetime_as
Почему-то ощущение что это какой-то костыль.
На счет того, что какой-нибудь:void fun(const char* p) { const SomeStruct* str = reinterpret_cast<const SomeStruct*>(p) ...
Считается UB, мнения постоянно делятся и из-за этого родилось, то что родилось.
Все неопределенное поведение тут завязано только на отправителе или источнике указателя p, и к текущему коду никакого отношения не имеет. Как например, если этот указатель был получен из другого языка или компилятора где char 2 байта или имеет специфическое выравнивание. То есть вся проблема из-за расплывчатого описания стандарта. В этом смысле start_lifetime_as тоже мало чем поможет. А сама пометка неопределенного поведения подразумевает что угодно. Конкретно здесь несоответственное расположение данных полям структуры SomeStruct и тому что объект, который будет использоваться как SomeStruct, на самом деле им не был.
То есть можно сделать вывод, что start_lifetime_as просто затычка, чтобы люди закончили по этому поводу спорить. Но это же ни чего не меняет. По сути тот же const SomeStruct* str = (const SomeStruct*)p. из С, который по своей природе в этом плане тоже не определен.void ReceiveData(std::span<std::byte> data_from_net) {
Из примера видно что данные получены не внутри компилированной программы. Соответственно start_lifetime_as никак не помогает. Тем более это набор байт, а не другая структура, что в принципе не дает для start_lifetime_as ни какой информации о исходном типе.
mayorovp
26.07.2022 14:50Вот только проблема строго обратная: если указатель p был получен "из другого языка или компилятора" (т.е. из внешних по отношению к компилятору штук, про которые он не знает) — то reinterpret_cast работает без всякого UB. Разумеется, если фактическое расположение и выравнивание полей соответствует ожидаемому.
А вот когда указатель p был сформирован внутри компилируемой программы — тут-то UB и вылезает на свет, угрожая покорёжить программу на этапе оптимизации. И именно здесь start_lifetime_as очень даже спасает.
panteleymonov
26.07.2022 15:51А вот когда указатель p был сформирован внутри компилируемой программы — тут-то UB и вылезает на свет, угрожая покорёжить программу на этапе оптимизации.
Вы понимаете что здесь точно также можно сказать что "Разумеется, если фактическое расположение и выравнивание полей соответствует ожидаемому — то reinterpret_cast работает без всякого UB." ? Кроме того также выделение памяти под объект выполнено соответствующими средствами С++. В результате эти примеры равнозначны.
И именно здесь start_lifetime_as очень даже спасает.
По вашему описанию выглядит как static_cast.
А из примера:
void process(Stream* stream) { std::unique_ptr buffer = stream->read(); if (buffer[0] == FOO) processFoo(reinterpret_cast(buffer.get())); // undefined behaviour else processBar(reinterpret_cast(buffer.get())); // undefined behaviour }
Видно что проблема здесь опять не в reinterpret_cast, а в том что buffer временный. Что из примера в статье выше это ни как не видно. И тут тоже можно обойтись исправлением без start_lifetime_as, копирования и UB.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2590r2.pdf
mayorovp
26.07.2022 16:17Вы понимаете что здесь точно также можно сказать что "Разумеется, если фактическое расположение и выравнивание полей соответствует ожидаемому — то reinterpret_cast работает без всякого UB."
А вот и нет. Выделить массив байт, кастануть его в указатель на структуру и обратиться к полю — это UB в плюсах независимо ни от каких выравниваний и прочего.
Видно что проблема здесь опять не в reinterpret_cast, а в том что buffer временный.
С чего бы тут была проблема из-за временного буфера?
panteleymonov
26.07.2022 17:02-1А вот и нет. Выделить массив байт, кастануть его в указатель на структуру и обратиться к полю — это UB в плюсах независимо ни от каких выравниваний и прочего.
Во первых, а вои и да:
Since C++20, certain functions in the C++ standard library such as malloc, bit_cast, and memcpy are defined to implicitly create objects [P0593R6]. As a result, the following code is no longer undefined behaviour:
struct X { int a, b; }; X* make_x() { X* p = (X*)malloc(sizeof(struct X)); // implicitly creates an object of type X p->a = 1; p->b = 2; return p; }
Хотя опять же это все определенные условия, при которых это можно было делать и раньше. Но
Во вторых, это утверждение по прежнему равнозначно для обоих примеров.
С чего бы тут была проблема из-за временного буфера?
Немного ошибся с переводом, но тем не менее читайте описание start_lifetime_as и того что оно на самом деле делает, а не то что вы предполагаете (ссылка у меня выше и в статье). Это то, из-за чего нельзя сначала сделать malloc, а потом delete. (чего нормальному человеку в голову не придет) Собственно это и есть те условия, обходя которые можно уйти от UB.
mayorovp
26.07.2022 17:41+3Это вы читайте внимательнее. Некоторые функции, такие как malloc, были явно "благословлены" — но не ко всем функциям выделения памяти это относится. В частности, при замене malloc на любой другой аллокатор UB возвращается, если реализация выделения памяти доступна компилятору:
But what about non-standard memory allocation or memory mapping functions that are provided by the user? Consider, for example, a library providing a memory pool, where the storage reuse is expressed in C++ code rather than in a syscall and is visible to the compiler. The current C++20 wording does not provide a solution for this use case, and code using such storage will be undefined behaviour.
panteleymonov
27.07.2022 15:53-1Опять вы путаете теплое с мягким и вводите в заблуждение других. Вопрос был про "Выделить массив байт" и "А вот когда указатель p был сформирован внутри компилируемой программы", где по вашему якобы помогает start_lifetime_as. И любому знающему человеку должно быть понятно в чем разница между разными парами "выделить"/"удалить" память, поскольку это разные менеджеры/системы памяти. Что собственно и делает start_lifetime_as, отключая у неявного объекта деструктор и конструктор. Поэтому ни какое ваше "кастануть его в указатель на структуру и обратиться к полю - UB" неверно ни для одного стандарта, до тех пор пока вы явно или неявно не вызвали деструктор или конструктор и не начали оперировать с таким объектом стандартными методами.
PyerK
26.07.2022 15:19+1std::get<1>
для агрегатовЭто magic_get для структур из коробки ? :)
antoshkka Автор
26.07.2022 16:32+2PyerK
26.07.2022 17:03+2Отличная инициатива, естественное и логичное развитие для языка после того, как появился structured binding !
Kelbon
26.07.2022 17:38+1Кажется там упущены перегрузки get для T&& и const T&& ?
И ещё хотелось бы подобный интерфейс для сумм типов, через get_if и распространить std::visit на все подобные типы, а не только вариант
JordanCpp
26.07.2022 16:22Думаю, что С++ это один из немногих языков, который не оставляет никого равнодушным. Его или ненавидишь сильно люто, или любишь и обожаешь. Другого не дано:)
ReadOnlySadUser
27.07.2022 10:39+3Я вот его сильно люто ненавижу, но продолжаю писать на нём. Отчасти потому, что ничего другого не знаю, отчасти потому, что жалко бросать, отчасти потому, что ощущение от знания С++ - как будто выучил китайский язык в мире ЯП и это тешит твоё эго за ушком.
В общем, я люблю и ненавижу его)
JordanCpp
27.07.2022 11:50-1Я люто обожаю. Любовь с первого коммита:)
ReadOnlySadUser
27.07.2022 11:56+1Давно ли был этот первый коммит? :) А то лет 10 назад я тоже был в восторге)
JordanCpp
27.07.2022 12:00Около 10-ти лет назад. На плюсах писал в общей сложности около трех лет. Потом всякие C#.
ReadOnlySadUser
27.07.2022 12:08+1Так не честно) Если С++ в роли любовницы, то понятно, что будешь его любить :)
artemisia_borealis
27.07.2022 12:39Любовь с первого коммита:)
т.е. тогда уже были коммиты! :)JordanCpp
27.07.2022 12:46Это просто выражение мысли. Написал законченную задачу на плюсах. И так сказать влюбился:)
buldo
26.07.2022 20:35+1А если я могу использовать только Cmake для сборки, есть какие-то рабочие варианты использовать модули?
antoshkka Автор
26.07.2022 21:47Стандартные модули в собранном виде должны поставляться из коробки. Если модуль находится по нестандартному пути, то путь до директории с модулями можно указать флагом компилятору. В обоих случаях, никаких проблем с cmake быть не должно.
Если планируете собирать собственный модуль, то придется написать немного cmake текста для создания таргета и его установки. Со временем писать придётся меньше, так как добавят нужный функционал https://gitlab.kitware.com/cmake/cmake/-/issues/18355
crackedmind
27.07.2022 15:24+1Да модули в смейке уже планируются в 3.25, https://gitlab.kitware.com/cmake/cmake/-/merge_requests?label_name=area%3Acxxmodules&milestone_title=3.25.0&state=all
agmt
27.07.2022 05:49+1`std::start_lifetime_as` вводит новые концепции?
std::start_lifetime_as actually creates a new object and starts its
lifetime (even if no code runs). On the other hand, std::launder never creates a new object, but
can only be used to obtain a pointer to an object that already exists at the given memory location
Что порождает несколько вопросов:
1. В примерах std::start_lifetime_as используется только для POD. Разве POD — не просто набор байт нужного размера с выравниванием?
2. Что за новая концепция «creates a new object»? И в чём её особенности для POD?
3. Если мы создали новый объект, то как его корректно уничтожить? И как не задеть уничтожение объекта, который тоже там находится (char[])?
4. Надо ли проводить такие же манипуляции для превращения POD/POD's fields в байты (например, при вызове memcpy() при подготовке к отправке по сети)?mayorovp
27.07.2022 12:21+2Это довольно древняя концепция, и start_lifetime_as не вводит её, а затыкает дыру которая мешала писать оптимальный и соответствующий стандарту код.
Стандарт С++ описывает некоторую абстрактную виртуальную машину С++. И память в этой виртуальной машине не линейная, а состоит из набора массивов, содержащих объекты, которые могут содержать вложенные объекты. А у этих объектов есть время жизни. И операция разыменования ссылок и указателей определена только тогда, когда они указывают на живой объект, в противном случае наступает UB — ситуация, поведение в которой стандартом не определяется и может быть произвольным.
Разумеется, реальная память линейная (по крайней мере, на популярных архитектурах). Но это не означает что любые операции указателями автоматически становятся до-определены! Поскольку с точки зрения стандарта при UB допустимо абсолютно любое поведение, компиляторы активно этим пользуются для оптимизации.
В примерах std::start_lifetime_as используется только для POD. Разве POD — не просто набор байт нужного размера с выравниванием?
POD — это не только набор байт нужного размера с выравниванием, но ещё и объект, у которого есть его время жизни. Если время жизни объекта не началось или уже закончилось — обращаться к нему нельзя даже если вы совершенно точно знаете какие байты лежат по этому адресу.
Если мы создали новый объект, то как его корректно уничтожить? И как не задеть уничтожение объекта, который тоже там находится (char[])?
Пока что способов уничтожить такой объект нет. Задача-то была в том, чтобы до-определить в терминах абстрактной машины некоторые полезные операции, требующие живого объекта, и она решена. Поскольку для "мёртвых" объектов никаких дополнительных полезных операций нет — то и явно приводить объект в "мёртвое" состояние не имеет смысла.
Надо ли проводить такие же манипуляции для превращения POD/POD's fields в байты (например, при вызове memcpy() при подготовке к отправке по сети)?
Нет, не надо. Стандарт явно разрешает работать с любым объектом как с набором байт, проблемы были лишь при попытке работы с набором байт как с объектом.
agmt
27.07.2022 13:02+1Стандарт явно разрешает работать с любым объектом как с набором байт
(char[], unsigned char[], signed char[]) или любой фундаментальный тип?
Просто иначе странная ситуация: offsetof() существует, фундаментальный объект существует, а их обоих в сборе — нет.
Upd: вот это имелось в виду?An operation that begins the lifetime of an array of char, unsigned char, or std::byte implicitly creates objects within the region of storage occupied by the array.
проблемы были лишь при попытке работы с набором байт как с объектом.
А какие проблемы? Компилятор может соптимизировать чтение из char* до и после изменения POD по тому же адресу?
А главное — зачем вводить новую функцию, а не «просто» перенести этот функционал на type cast "(T*)"?mayorovp
27.07.2022 14:19(char[], unsigned char[], signed char[]) или любой фундаментальный тип?
Просто иначе странная ситуация: offsetof() существует, фундаментальный объект существует, а их обоих в сборе — нет.Честно говоря, я не понял что вы сейчас спросили.
Upd: вот это имелось в виду? An operation that begins the lifetime of an array of char, unsigned char, or std::byte implicitly creates objects within the region of storage occupied by the array.
Нет, это отдельный костыль, созданный чтобы PODы можно было создавать при помощи malloc, иначе совсем несовместимо с Си получалось.
А какие проблемы? Компилятор может соптимизировать чтение из char* до и после изменения POD по тому же адресу?
Именно так. Или может выкинуть ветку условного оператора увидев такое чтение. Или может прочитать нули вместо данных.
Впрочем всё это, вроде как, проблемы гипотетические, поскольку современные компиляторы этим не занимались.
А главное — зачем вводить новую функцию, а не «просто» перенести этот функционал на type cast "(T*)"?
Потому что тайпкаст уже задействован для получения указателя на существующий объект. Если он будет иногда работать как раньше, а иногда создавать новый объект — все запутаются.
agmt
27.07.2022 14:30Честно говоря, я не понял что вы сейчас спросили.
Не очень понятно, где проходит граница UB.struct header_t { uint16_t type; uint8_t mask; }; uint8_t buf[512]; auto r1 = ((header_t*)buf)->mask; // UB auto r2 = (new(buf) header_t)->mask; // No UB auto r3 = *(buf + offsetof(header_t, mask)); // No UB? auto r4 = *((uint16_t*)(buf + offsetof(header_t, type))); // UB?
mayorovp
27.07.2022 14:44+2r1 — UB
r2 — нет никакого UB, объект был создан оператором new
r3 — никакого UB, тут разыменование корректного указателя
r4 — UB, на самом деле этот пример такой же как и первыйeao197
27.07.2022 14:57+3На счет r2 не все так однозначно, для buf следовало бы использовать alignas(header_t). ИМХО.
agmt
27.07.2022 14:59Спасибо за пояснения.
Краткий вывод: можно использовать type cast только при:
1. приведении к базовому/наследному классу (они существуют);
2. приведении из void*, например, в pthread_create()'s start_routine (объект существует).
Во всех остальных случаях надо использовать placement new/std::start_lifetime_as.
Vernat
27.07.2022 09:53+2Когда компиляторы начнут полностью поддерживать с++20 ?!
antoshkka Автор
27.07.2022 12:01В C++23 было много багфиксов в C++20. Сейчас вроде пыль улеглась, так что стандартные библиотеки начнут активнее новый функционал реализоввывать. Ядерная же часть языка C++20 подтормаживает, и не понятно сколько будет тормозить: корутины и модули оказались крайне сложными для реализации.
JordanCpp
27.07.2022 12:59Планирует коммитет запилить нормальные, стандартные, быстрые regexp'ы? Типа таких но в стандарте https://github.com/hanickadot/compile-time-regular-expressions
И еще доп. вопрос. Будет ли комитет выпускать расширения стандарта? К примеру добавлять с расширением только библиотеки, на язык они не влияют, компилятор допиливать не требуется. 3 года ждать для нужного функционала долго. Хочется больше стандартных, быстрых библиотек для парсинга, веба и т.д
eao197
27.07.2022 14:50+2Хочется больше стандартных, быстрых библиотек для парсинга, веба и т.д
Лично мне меньше всего хочется видеть в стандарте библиотеки для работы с HTTP, XML, JSON, YAML и чем-то подобным.
Такое должно жить в сторонних библиотеках.
А вот какой-то штатный пакетный менеджер и вменяемая штатная система сборки необходимы (хотя я и не представляю себе как такое в сложившихся реалиях возможно). Тогда проблем с тем, чтобы взять для работы с RESTful какие-то сторонние либы для HTTP, JWT, JSON, не будет вообще. И незачем все это Г, которое рано или поздно устареет, тащить в стандарт.
ЗЫ. Просто в качестве примера: в середине нулевых XML был "нашим всем", пихали его во все дырки без устали и проблесков здравого смысла. На его же фоне еще и SOAP расцветал буйным цветом, хотя где-то рядом еще и XML-RPC трепыхался. Ну и где это все спустя 15-17 лет? Так что еще лет через 15 мы можем тоже самое увидеть и по отношению к JSON, HTTP и прочим мейнстримовым сейчас технологиям.JordanCpp
28.07.2022 10:01+2Отчасти согласен, хотя есть контр пример. Ranges в стандарте. Столько потратили времени на их проработку, исправление, а их юзает 2.5f человека. Header занимает 1.5 мб.
Если библиотеку не юзают. Всегда можно сделать деприкатед и исключить. http и json это базовые библиотеки для разработки web'a я бы еще добавил протокол fcgi.
eao197
28.07.2022 10:25+1хотя есть контр пример. Ranges в стандарте
Сколько они в стандарте? Сколько компиляторов сейчас полностью поддерживают C++20? Сколько проектов, особенно с длительной историей, успело переключиться на C++20? Сколько курсов в ВУЗах (пусть даже в СНГ) успело включить ranges в программу?
Рано судить о том, сколько людей юзают ranges. Вы бы еще сегодня сделали далеко идущие выводы о востребованности короутин из C++20.
Всегда можно сделать деприкатед и исключить.
Да вы что? Самому приходилось нести ответственность за проект с n-летней историей, который пришлось переводить с C++98 на C++11? Например, замена auto_ptr на unique_ptr хоть и делалась тривиально, но это была работа, которую нужно было кому-то работать. Которая становится еще более веселой, если приходится какое-то время держать ветку и для C++98, и для C++11.
http и json это базовые библиотеки для разработки web'a
Этих библиотек (для http в частности) сейчас можно сходу пяток назвать, если не десяток. Какая из них должна войти в stdlib и почему? Какой интерес затем делать что-то лучше, если в стандарт войдет какой-нибудь Boost.Beast и куча народу со временем начнет кричать "а нам лучшего и не нужно".
Мало критики было на std::regex и std::unordered_map, наверное. Хотя пора было бы уже на ошибках учиться.
Впрочем, зачем http и json ограничиваться. Давайте еще и GUI в stdlib засунем. Не, у а чё? Разработка десктопа на C++ все еще одна из тех ниш, где C++ пока что нужен и держит конкуренцию. А еще криптографию в stdlib нужно запихнуть, а то зачем выбирать OpenSSL, botan, Crypto++ или еще что.
И ведь этот список можно продолжать. Почему бы не иметь в стандарте аналог Threading Building Block. Или tensorflow. Или нужное вписать.
Зачем останавливаться, если потом можно сделать деприкатед и исключить?
JordanCpp
28.07.2022 10:45Сколько они в стандарте? Сколько компиляторов сейчас полностью поддерживают C++20? Сколько проектов, особенно с длительной историей, успело переключиться на C++20? Сколько курсов в ВУЗах (пусть даже в СНГ) успело включить ranges в программу?
Рано судить о том, сколько людей юзают ranges. Вы бы еще сегодня сделали далеко идущие выводы о востребованности короутин из C++20.
Статья до и после Ranges
https://habr.com/ru/company/otus/blog/456452/
По мне лучше бы в С++11 модули добавили, уже с десять лет бы программисты их юзали. Я говорю о добавлении практичных возможностей, для работы с вебом, парсингом актуальных форматов. Что тагого принесли ranges?
Да вы что? Самому приходилось нести ответственность за проект с n-летней историей, который пришлось переводить с C++98 на C++11? Например, замена auto_ptr на unique_ptr хоть и делалась тривиально, но это была работа, которую нужно было кому-то работать. Которая становится еще более веселой, если приходится какое-то время держать ветку и для C++98, и для C++11.
Здесь альтернативы нет, весь груз ложится на плечи разработчиков.
Этих библиотек (для http в частности) сейчас можно сходу пяток назвать, если не десяток. Какая из них должна войти в stdlib и почему? Какой интерес затем делать что-то лучше, если в стандарт войдет какой-нибудь Boost.Beast и куча народу со временем начнет кричать "а нам лучшего и не нужно".
Мало критики было на std::regex и std::unordered_map, наверное. Хотя пора было бы уже на ошибках учиться.
Впрочем, зачем http и json ограничиваться. Давайте еще и GUI в stdlib засунем. Не, у а чё? Разработка десктопа на C++ все еще одна из тех ниш, где C++ пока что нужен и держит конкуренцию. А еще криптографию в stdlib нужно запихнуть, а то зачем выбирать OpenSSL, botan, Crypto++ или еще что.
И ведь этот список можно продолжать. Почему бы не иметь в стандарте аналог Threading Building Block. Или tensorflow. Или нужное вписать.
Зачем останавливаться, если потом можно сделать деприкатед и исключить?
Я говорю о базовых библиотеках. GUI возможно и стоит внести, в java есть несколько штук и нормально живут и прогают. Хочу java без java:) Стандарт С++ он сам по себе мал. Если даже добавить 10 библиотек он вырастет в два раза. Если сравнивать со стандартными библиотеками python или java, это даже не рядом.
Криптографию, разные аллокаторы, больше контейнеров, алгоритмов, криптографий. То, что юзают каждый день в стандарте маловато реализовано, а всякие ranges есть.
eao197
28.07.2022 10:59+2Статья до и после Ranges
Как она отвечает на заданные мной вопросы?
Если вам кажется, что ranges ничего не вносят и бесполезны, то могу вам привести пример из давней (для вас особенно) истории: STL и <algorithm> в частности. Стандартизировали в 1998-ом, исходники RogueWave STL ходили по Интернету года с 1996-го, наверное. Сколько лет потом люди вместо std::find/find_if или std::accumulate вручную for-ы выписывали? Да лет 10, если не 15, минимум. Некоторые и до сих пор выписывают.
Ranges -- это такая штука, последствия которой нужно будет оценивать лет через 10, не раньше.
По мне лучше бы в С++11 модули добавили
Если бы у бабушки было известно что, то это был бы дедушка. История не знает "если бы". Не приняли в C++11 модули (я даже не помню, была ли тогда речь о модулях вообще). Концепты, которые должны были стать киллер-фичей C++11 и много обсуждались года с 2007-го, в язык попали только в C++20.
Так что история развивается так, как развивается. И нам приходится иметь дело с тем, что есть, а не тем, что хотелось бы.
Я говорю о добавлении практичных возможностей, для работы с вебом, парсингом актуальных форматов.
Я вам прозрачно намекаю, что в стандарт не впихнешь все, что кому-нибудь нужно.
Вы не поверите, но в мире C++ далеко не всем нужна работа с Web-ом. А кому она нужна, те уже закрыли свои нужды как с помощью полудюжины живых OpenSource проектов, так и с помощью своих внутренних, закрытых разработок. Причем, если где-то делают серьезный Web на C++, то там и требования специфические, там штатная реализация Web-сервера из stdlib вряд ли будет востребована (по аналогии с тем, как сейчас востребован std::regex там, где важна скорость).
Здесь альтернативы нет, весь груз ложится на плечи разработчиков.
А если из стандарта десятилетиями ничего не выкидывать, то на разработчиках не будет этого груза ответственности, представляете?
Если даже добавить 10 библиотек он вырастет в два раза.
А теперь главный вопрос: кто будет оплачивать сей банкет?
svr_91
28.07.2022 11:29Сколько лет потом люди вместо std::find/find_if или std::accumulate вручную for-ы выписывали?
А интересно, действительно ли нужны все эти тривиальные стандартные алгоритмы, которые не делают какую-нибудь нетривиальную вещь типа бинарного поиска? Может, и правда иногда проще написать for руками? Тоесть да, понятно, что на пиар этих функций ушло довольно много ресурсов, но как бы повернулась история, если бы этого всего не было бы? (Причем я не только в рамках C++ это рассматриваю)
eao197
28.07.2022 11:34+2Может, и правда иногда проще написать for руками?
Тривиальные алгоритмы, как минимум, абстрагируют вас от конкретного типа контейнера. Сегодня у вас какой-нибудь `MyType items[10]`, завтра `std::vector<MyType> items`, послезавтра `std::list<MyType> items`, а через месяц `std::array<MyType, 1024> items`. И вам не нужно будет менять код, написанный в стиле `find(begin(items), end(items), value)`.
svr_91
28.07.2022 11:46+2Ну, контейнеры меняются не так часто, по крайней мере по моему опыту. Если меняется контейнер с vector на list например, или наоборот, то проблем оказыватеся больше, чем просто отследить все места, где он используется.
Просто по моему ощущению, stl алгоритмы очень хороши на учебных примерах, а в реальной жизни довольно часто сталкиваешься с какими-нибудь ограничениями, и нужно или долго и мучительно выдумывать, как скомбинировать алгоритмы друг с другом, или плюнуть и написать свой for.
eao197
28.07.2022 11:52Ну, контейнеры меняются не так часто,
It depends.
или плюнуть и написать свой for
Я так понимаю, что это была одна из целей для ranges. Ну, чтобы композабильность алгоритмов сильно улучшить.
svr_91
28.07.2022 11:56Возможно. Просто мысль была такая (только сейчас ее смог сформулировать).
Вот возьмем управление памятью: сборщик мусора в большинстве языков, деструкторы (то бишь, unique_ptr, shared_ptr и т.п.) в C++. Вродебы очевидно, что если все эти подходы взять и выбросить и заставить программистов писать в стиле C, с ручным управлением памятью, то будет сильно-сильно больно. Тоесть все эти идеи и правда дают какую-то пользу. А вот если также взять, и выбросить все stl-и, range, linq и т.п., что изменится? Ну да, первую неделю программисты будут хмыкать, не найдя любимой функции, а потом, никто и не вспомнит об этом, или что-то да изменится?
eao197
28.07.2022 12:02+1А вот если также взять, и выбросить все stl-и, range, linq и т.п., что изменится?
Одна из задач, которая решается стандартной библиотекой, -- это организация "общего словаря", который позволяет общаться кускам кода от разных разработчиков. Т.е., если либа A принимает на вход std::string и возвращает std::string, то ее легко подружить с либой B, которая принимает на вход std::string и возвращает std::string.
Если у нас такого "общего словаря" нет (либо он низведен до int, float, double и char*), то:
каждая либа (или каждый проект) будет переизобретать собственную базовую библиотеку (грубо говоря у всех свой собственный string);
при необходимости задействовать либы A и B в одном проекте придется преобразовывать string из либы A в string из либы B. И наоборот.
Все это уже проходилось в 1990-е, последствия расхлебывались до конца нулевых. Спасибо, больше не хочу.
eao197
28.07.2022 12:09Мне сложно отделить одно от другого, т.к. сила STL, когда он только появился, была именно в том, что разработчикам дали a) набор необходимых базовых типов и b) набор алгоритмов, которые могут работать над этими самыми базовыми типами (и не базовыми, если они мимикрируют под базовые).
Это уже со временем и опытом выяснилось, что внешние итераторы не всегда есть гуд.
А изначально это был серьезный прорыв. Переход на другой уровень абстракции. Который требует некоторого сдвига по фазе в голове у разработчика (не такой серьезный, как при переходе от процедурного программирования к ООП или от ООП к ФП, но все-таки).
Были бы в STL только стандартные контейнеры, людям бы пришлось бы писать свои аналоги функций из <algorithm>. Хорошего в этом было бы не много.
eao197
28.07.2022 16:08Забыл добавить про обобщенное программирование. Вот если мы пишем шаблонный код, в который кто-то передает контейнер (мы не знаем что именно это за контейнер: list, vector или deque) и нам нужно что-то найти в этом контейнере, то std::find нам поможет. А вот без std::find в шаблонном коде как делать простой поиск? Писать вручную for на обобщенных итераторах? Так ведь получим тот же самый std::find. Только вот std::find написан один раз, а не будь его, то всем пришлось бы свой аналог делать.
svr_91
28.07.2022 16:17-1Хоть я изначально и ограничил обсуждение тривиальными алгоритмами, но давайте вернемся к нетривиальному алгоритму, например sort. Для него в листе отдельный метод, ибо как обычный не работает. Ну возможно что-то похожее будет, невелика потеря.
И итераторы не нужны, итерироваться по индексам.
mayorovp
28.07.2022 16:20+2Ага, особенно в двусвязном списке или в дереве по индексам итерироваться удобно...
eao197
28.07.2022 16:33+1Для него в листе отдельный метод, ибо как обычный не работает.
Так ведь никто и не обещал, что обобщенное программирование -- это серебряная пуля. Не зря же в C++ при добавлении шаблонов в язык сделали возможность специализации, поскольку изначально было понятно, что местами обобщенное решение не будет хорошим.
И итераторы не нужны, итерироваться по индексам.
Так ведь индексация не для всех типов будет эффективной.
svr_91
28.07.2022 16:39Так ведь индексация не для всех типов будет эффективной.
Я понимаю, естественно. Могу лишь повторить вашу фразу, что "обобщенное программирование - не серебрянная пуля". Без алгоритмов stl она была бы еще чуть-чуть менее серебрянной
mayorovp
28.07.2022 16:19Был бы с самого начала доступен range for — можно было бы и его писать каждый раз. Но range for решили не вводить, всякие std::find показались удобнее...
eao197
28.07.2022 16:24Я сомневаюсь, что в начале 1990-х обсуждался range for. Тогда как std::find в те времена можно было сделать не меняя языка. И сделали, что характерно, в виде отдельной либы. Это потом уже STL стал расшифровываться как Standard Template Library.
JordanCpp
28.07.2022 10:47Чё там с utf, осилили?;)
Videoman
28.07.2022 18:09Кстати, полностью поддерживаю сарказм. Вроде как сначала имели конвертеры в стандарте, потом в С++17 их вдруг задепрекейтили, без объяснений. Ничего нового не завезли. С u8"" строками вообще не понятно. Допустим до С++20 в качестве контейнера для UTF-8 я могу использовать std::string и u8"" строки, которые сейчас преобразуются к char. Интересно, как предполагается работать с UTF-8 в дальнейшем, когда у нас появляется тип char8_t, что с обратной совместимостью к тоннам уже существующих API?
Пока приходится пользоваться своим велосипедом: sutfcpplib, но почему-то ничего готового не появляется в стандарте, хотя бы базовые вещи :(?
JordanCpp
28.07.2022 10:52Я знаю о существовании гитхаба, бери и юзай.
eao197
28.07.2022 11:05+2Если бы все проекты на C и C++ были снабжены хотя бы нормальными проектными файлами для CMake (путь это и Г, но это хоть какой-то де-факто стандарт в нашем мире), то этот подход бы более-менее работал.
Но это не так.
Временами нужно потратить туеву хучу времени на то, чтобы встроить чужую библиотеку в свой проект.
vcpkg/conan постепенно меняют ситуацию в лучшую сторону. Но, насколько я могу судить, до удобства других языков (Java с Maven или Rust с cargo) тут еще как до Пекина раком :(
JordanCpp
28.07.2022 11:11CMake распространенное решение. Я вообще с подозрением смотрю на библиотеки общего использования без файлика CMakeLists.txt
Возможно со временем добавят и пакетный менеджер. В С++43:)
eao197
28.07.2022 11:16Я вообще с подозрением смотрю на библиотеки общего использования без файлика CMakeLists.txt
Вот еще бы разработчики FFMPEG ваше мнение разделяли бы...
JordanCpp
28.07.2022 12:28+1Насколько я понимаю, разработчики ffmpeg'а старорежимники староверы. CMake всего 20 лет, вдруг не выстрелит.:)
eao197
28.07.2022 12:29-2А вы здесь чтобы зубы поскалить или что-то серьезное обсудить? Если первое, то без меня, пожалуйста.
JordanCpp
28.07.2022 12:36Ну извините. Не смею вас отвлекать, своими наветами. Серьезный разговор, премного благодарен вашему ответу, обуздавшее мое поверхностное суждение, дабы узрел я пороки зубоскальства, крестился и встал на путь истинного С++ программиста. Ибо смех от лукавого, да не узрит ваше око, мою беспечность.
antoshkka Автор
27.07.2022 16:32Ханна работала над продвижением CTRE в стандарт, сейчас кажется отвлеклась на статическую рефлексию.
Расширения в стандарт уже есть, они называются TS. Например из недавнего - Transactional Memory TS
PyerK
27.07.2022 19:37+1Какая судьба чистых ( pure ) функций ? идёт ли работа в этом направлении ?
antoshkka Автор
28.07.2022 07:46+1Работа не идёт, последнее предложение было в 2013 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3744.pdf
NickSin
Такими темпами нужно будет отдельный курс в институте на 5 лет вводить "Знаток с++"
iliketech
Я б в такой институт не просто пошел.. я б туда побежал)) Люблю плюсы)
NickSin
Плюсы все равно уже стали слишком раздуты. Я и 1/9 всех фич не использую. Некоторые даже никогда на практике не видел.
Kelbon
например?
NickSin
Все, что тут было описано я не знал и не использовал)
Kelbon
может потому что этого ещё нет в С++? И будет только в С++23?
NickSin
Да и фичи их с++20 не использую. Просто не знаю их толком, да и не нужны в моем деле. Очень раздувается в плане фич стандарт и становится очень тяжело следить.
Kelbon
вы не можете говорить, что они не нужны вам, если вы не знаете их. 99%, что пригодилось бы что то и очень сильно
NickSin
Возможно. Просто становиться все сложнее и сложнее это мониторить и понимать.
JordanCpp
Я понимаю о чем вы. У вас иная специфика, ваша задача обучение языку С++. В проде могут через пятилетки переходить на следующий стандарт. И это следующий стандарт может оказаться С++17:)
NickSin
Я еще в НИРах использую плюсы. Поэтому мне тоже надо смотреть на это все. К тому же, может прийти заказчик НИРа и попросить реализовывать все это на 14/17 стандартах.
JordanCpp
Замечу, что стандарт раздувается, но язык становится проще. Не в плане количества фич, а в реализации своих идей. Меньше приходится выходить на тропку С, умные указатели, контейнеры и т.д Когнитивная нагрузка бесспорно возрастает, но если юзать некий допустимый минимум и далее добавлять фичи по мере надобности, вполне по силам.
NickSin
Ну вот, а потом проблема искать эти новые фичи и нужны ли они в твоем случае. Я про это имею ввиду.
JordanCpp
Мем для поднятия настроения:)
NickSin
Так и есть)
ReadOnlySadUser
Я не согласен, ибо все эти упрощения зачастую - это жутко протекающие абстракции. И пока они работают - всё хорошо, но как только что-то пошло не так, процесс расковыривания может потянуть на маленькую диссертацию.
Так, например, я вообще понятия не имею как устроены ranges. И когда я их последний раз трогал, но они не работали как мне хотелось, несколько часов было потрачено на попытки разобраться во внутрянке и... Не получилось.
В общем, в итоге я просто отказался и переписал всё без них.
JordanCpp
Я сразу понял, что к ним я не притронусь. Слишком оверхедно для моего мозга:)
JordanCpp
Без знания железа и как процессор все это обрабатывает, писать эффективный код на С++ невозможно. К примеру я байтоложец:) Любитель во всякую низкоуровщину, и при написании кода, всегда держу в голове, выравнивание, размер типов, влезет ли в кеш и т.д
По работе пишу на C#, и понимаю, что многие знания почти не применимы. Просто пишу высокоуровневый код и не парюсь.
ReadOnlySadUser
Это правда, но если ты один фиг об этом думаешь и знаешь, зачем скрывать это под толстенным слоем абстракций, ведь при их чтении - один фиг придётся думать о том, как байты лежат)
Для меня С++17 пока что идеальный стандарт. Он очень аккуратно исправил недостатки предшественников, не создавая каких-то аццких синтаксических монстров.
Из всего С++20, полезными я вижу только концепты и гетерогенные unordered контейнеры. Из представленного в С++23, возможно только std::expected, std::print и новые контейнеры.
antoshkka Автор
А как же std::stacktrace ? Классная ведь штука :)
ReadOnlySadUser
Прикольная, но ввиду того, что уже каждый первый себе написал аналог - немножечко бесполезная) Не работал ни на одном проекте, который бы себе уже не написал этот функционал.
Возможно, для проектов, которые стартанут году эдак а 2030-м, фича сэкономит пару-тройку часов какому-то программисту)
JordanCpp
Черный пояс, по указателям. Седьмой дан по трейтам. Чемпион по UB:)