constexpr функции сквозь года
#новичкам
constexpr бывают не только переменные, но и функции. Такие функции могут быть выполнены, как в compile-time, так и в runtime, в зависимости от контекста вызова:
- Если значения параметров возможно посчитать на этапе компиляции, то возвращаемое значение также должно посчитаться на этапе компиляции.
- Если значение хотя бы одного параметра будет неизвестно на этапе компиляции, то функция будет запущена в runtime.
- Если вы попытаетесь присвоить возвращаемое значение функции с runtime аргументом constexpr переменной, то получите ошибку компиляции.
Пройдемся по стандартам языка и посмотрим, как в них изменялись constexpr функции.
В С++11 можно было использовать только однострочные constexpr функции с сильными ограничениями.
В С++14 уже можно писать многострочные функции, использовать в них локальные переменные и базовые конструкции языка, кроме try-catch(там динамические аллокации, с которыми трудно в compile-time), goto(открестились и правильно сделали) и еще пары менее значимых моментов:
C++17 - constexpr лямбды. За это отдельный лайк 17-му стандарту, но помимо этого ничего существенного не привнеслось:
C++20 и выше - constexpr почти везде. Уже есть и виртуальные constexpr функции, и исключения можно в compile-time отлавливать, и большинство простых контейнеров могут быть созданы и препарированы в compile-time, и все больше алгоритмов и стандартных функции получают пометки constexpr.
constexpr функции помогают иметь единый интерфейс для runtime и compile-time вычислений. Поэтому использование таких функций может приводить к неожиданному переносу некоторых вычислений в compile-time.
В сферах, где важны ультра-мега-милипизерные задержки очень важно как можно больше действий перевести в compile-time + сильно разгрузить "разогрев" программы , чтобы оставить время выполнения только для самых важных вещей(лутание бабок).
Free up time for important things. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
#новичкам
constexpr бывают не только переменные, но и функции. Такие функции могут быть выполнены, как в compile-time, так и в runtime, в зависимости от контекста вызова:
- Если значения параметров возможно посчитать на этапе компиляции, то возвращаемое значение также должно посчитаться на этапе компиляции.
- Если значение хотя бы одного параметра будет неизвестно на этапе компиляции, то функция будет запущена в runtime.
- Если вы попытаетесь присвоить возвращаемое значение функции с runtime аргументом constexpr переменной, то получите ошибку компиляции.
constexpr int square(int x) {
return x * x;
}
int main() {
constexpr int val = square(5); // compile-time calculations
static_assert(val == 25, "Must be 25"); // compile-time check
int arg = rand() % 25;
int res = square(arg); // runtime calculations
assert(res == arg*arg); // runtime check
constexpr int fail = square(arg); // compile error here!
}Пройдемся по стандартам языка и посмотрим, как в них изменялись constexpr функции.
В С++11 можно было использовать только однострочные constexpr функции с сильными ограничениями.
constexpr int factorial(int n) {
// ternary operator is allowed, so recursion
return (n <= 1) ? 1 : n * factorial(n - 1);
}В С++14 уже можно писать многострочные функции, использовать в них локальные переменные и базовые конструкции языка, кроме try-catch(там динамические аллокации, с которыми трудно в compile-time), goto(открестились и правильно сделали) и еще пары менее значимых моментов:
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
static_assert(factorial(5) == 120, "Must be 120");C++17 - constexpr лямбды. За это отдельный лайк 17-му стандарту, но помимо этого ничего существенного не привнеслось:
constexpr auto factorial = [](int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
};
static_assert(factorial(5) == 120, "Must be 120");C++20 и выше - constexpr почти везде. Уже есть и виртуальные constexpr функции, и исключения можно в compile-time отлавливать, и большинство простых контейнеров могут быть созданы и препарированы в compile-time, и все больше алгоритмов и стандартных функции получают пометки constexpr.
constexpr std::string create_greeting() {
std::string s = "Hello, ";
s += "C++20!";
return s;
}
static_assert(create_greeting() == "Hello, C++20!");constexpr функции помогают иметь единый интерфейс для runtime и compile-time вычислений. Поэтому использование таких функций может приводить к неожиданному переносу некоторых вычислений в compile-time.
В сферах, где важны ультра-мега-милипизерные задержки очень важно как можно больше действий перевести в compile-time + сильно разгрузить "разогрев" программы , чтобы оставить время выполнения только для самых важных вещей(лутание бабок).
Free up time for important things. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
🔥31👍11❤6👎2
constexpr vs consteval функции
#опытным
В С++20 добавили новый спецификатор - consteval. Отдельно его интро не особо интересно разбирать, поэтому попробуем в сравнении с constexpr.
consteval может быть применен только к функциям и это ключевое слово заставляет функцию выполняться во время компиляции и возвращать константное выражение. Отсюда и название спецификатора "константное вычисление"(constant evaluation).
Вот пара простых примеров:
Мы можем использовать оба спецификатора constexpr и consteval для того, чтобы инициализировать compile-time константы и обычные переменные.
Однако consteval функции могут вызываться только с constant expression в качестве аргументов. При попытке передачи в них неконстантного выражения будет ошибка компиляции.
Итого:
👉🏿 спецификатор consteval может быть применен только для функций
👉🏿 constexpr может быть применен и для переменных
👉🏿 consteval заставляет компилятор вычислять выражение на этапе компиляции. Если хотя бы один аргумент такой функции не является константным выражением, то будет ошибка компиляции
👉🏿 constexpr функции могут быть вычислены на этапе компиляции, если аргументы являются константными выражениями. Но также они могут быть вычислены в рантайме с аргументами в виде рантайм значений.
Don't be confused. Stay cool.
#cpp20
#опытным
В С++20 добавили новый спецификатор - consteval. Отдельно его интро не особо интересно разбирать, поэтому попробуем в сравнении с constexpr.
consteval может быть применен только к функциям и это ключевое слово заставляет функцию выполняться во время компиляции и возвращать константное выражение. Отсюда и название спецификатора "константное вычисление"(constant evaluation).
Вот пара простых примеров:
consteval int sum_consteval(int a, int b) {
return a + b;
}
constexpr int sum_constexpr(int a, int b) {
return a + b;
}
int main() {
constexpr auto c = sum_consteval(100, 100);
static_assert(c == 200);
constexpr auto c1 = sum_constexpr(100, 100);
static_assert(c1 == 200);
constexpr auto val = 10;
static_assert(sum_consteval(val, val) == 2*val);
int a = 10;
int res = sum_constexpr(a, 10); // fine with constexpr function
int res1 = sum_consteval(10, 10);
// int res2 = sum_consteval(a, 10); // error!
// the value of 'a' is not usable in a constant expression
}Мы можем использовать оба спецификатора constexpr и consteval для того, чтобы инициализировать compile-time константы и обычные переменные.
Однако consteval функции могут вызываться только с constant expression в качестве аргументов. При попытке передачи в них неконстантного выражения будет ошибка компиляции.
Итого:
👉🏿 спецификатор consteval может быть применен только для функций
👉🏿 constexpr может быть применен и для переменных
👉🏿 consteval заставляет компилятор вычислять выражение на этапе компиляции. Если хотя бы один аргумент такой функции не является константным выражением, то будет ошибка компиляции
👉🏿 constexpr функции могут быть вычислены на этапе компиляции, если аргументы являются константными выражениями. Но также они могут быть вычислены в рантайме с аргументами в виде рантайм значений.
Don't be confused. Stay cool.
#cpp20
👍27🔥6❤4🗿2😁1
constexpr vs constinit
#опытным
constinit - еще один спецификатор, который появился в С++20. Им помечаются глобальные или thread-local переменные для того, чтобы удостоверится в их статической инициализации. Это либо нулевая инициализация, либо константная инициализация. Собственно, это и отображено в самом названии спецификатора.
Тут суть в Static Initialization Order Fiasco. Инициализация глобальных переменных в рантайме зависит от фазы луны и половой активности жаб в Центральной Америке. Мы нем можем гарантировать порядок инициализации глобальных переменных в рантайме и если одна переменная зависит от значения другой, то может произойти много неприятных неожиданностей.
Вот constinit служит гарантией того, что переменная проинициализирована до старта программы, а программистам показывает, что с этой глобальной переменной точно все в порядке.
Интересность ситуёвины состоит в том, что constinit не подразумевает константность объекта! Действительно, мы же можем проинициализировать константным выражение ь неконстантную переменную и это будет валидный код:
constinit - это только про гарантии инициализации в компайл тайме и все! Например:
Мы можем инициализировать global с помощью константных выражений, в том числе и результатами вычислений constexpr функций. Однако сама global не является ни constexpr, ни даже обычной константой. С ее помощью нельзя инициализировать другие constinit переменные, как нельзя использовать ее в качестве шаблонных параметров. Но global можно изменять, как как она не предполагает иммутабельность.
Вы также не можете определить constexpr constinit переменную, потому что будет масло масляное. constexpr и так обеспечивает статическую инициализацию глобальных переменных.
Итого:
👉🏿 constinit переменные в своей базе мутабельные, constexpr - немутабельные.
👉🏿 constinit применяется только к static и thread storage duration объектам. Проще говоря, к разного рода глобальным переменным. constexpr может применяться к локальным переменным.
👉🏿 Оба спецификатора обеспечивают инициализацию глобальных переменных в compile-time и защищают от SIOF.
👉🏿 Эти спецификаторы нельзя использовать в одном выражении.
Don't be confused. Stay cool.
#cpp20
#опытным
constinit - еще один спецификатор, который появился в С++20. Им помечаются глобальные или thread-local переменные для того, чтобы удостоверится в их статической инициализации. Это либо нулевая инициализация, либо константная инициализация. Собственно, это и отображено в самом названии спецификатора.
Тут суть в Static Initialization Order Fiasco. Инициализация глобальных переменных в рантайме зависит от фазы луны и половой активности жаб в Центральной Америке. Мы нем можем гарантировать порядок инициализации глобальных переменных в рантайме и если одна переменная зависит от значения другой, то может произойти много неприятных неожиданностей.
Вот constinit служит гарантией того, что переменная проинициализирована до старта программы, а программистам показывает, что с этой глобальной переменной точно все в порядке.
Интересность ситуёвины состоит в том, что constinit не подразумевает константность объекта! Действительно, мы же можем проинициализировать константным выражение ь неконстантную переменную и это будет валидный код:
static int i = 42;
constinit - это только про гарантии инициализации в компайл тайме и все! Например:
// init at compile time
constexpr int compute(int v) { return vvv; }
constinit int global = compute(10); // compute is invoked at compile-time
// won't work:
// constinit int another = global; // global is a runtime value
int main() {
// but allow to change later...
global = 100;
// global is not constant expression!
// std::array<int, global> arr;
}
Мы можем инициализировать global с помощью константных выражений, в том числе и результатами вычислений constexpr функций. Однако сама global не является ни constexpr, ни даже обычной константой. С ее помощью нельзя инициализировать другие constinit переменные, как нельзя использовать ее в качестве шаблонных параметров. Но global можно изменять, как как она не предполагает иммутабельность.
Вы также не можете определить constexpr constinit переменную, потому что будет масло масляное. constexpr и так обеспечивает статическую инициализацию глобальных переменных.
Итого:
👉🏿 constinit переменные в своей базе мутабельные, constexpr - немутабельные.
👉🏿 constinit применяется только к static и thread storage duration объектам. Проще говоря, к разного рода глобальным переменным. constexpr может применяться к локальным переменным.
👉🏿 Оба спецификатора обеспечивают инициализацию глобальных переменных в compile-time и защищают от SIOF.
👉🏿 Эти спецификаторы нельзя использовать в одном выражении.
Don't be confused. Stay cool.
#cpp20
👍23❤10🔥9❤🔥1
Висячие ссылки в лямбдах
#новичкам
Все знают, что возврат ссылки на локальный объект функции приводит к неопределенному поведению. Однако не всегда так просто можно распознать такие ситуации.
В C++11 появились лямбда-выражения, а вместе с ними ещё один способ прострелить себе причинное место.
Лямбда, захватывающая что-либо по ссылке, безопасна до тех пор, пока она не возвращается куда-либо за пределы области, в которой её создали. Как только лямбда покинула скоуп - можно начинать молиться:
Гцц и шланг пишут разный результат на консоль, что напрямую говорит об ub. Можете посмотреть тут. На варнинги об этой ситуации лучше не надеяться, потому что гцц например думает, что в коде все в порядке.
Еще более интересная ситуация с объектами и методами.
Что же здесь может провиснуть? Никаких локальных объектов в методе GetNotifier нет.
На самом деле провиснет сам объект, на котором вызывается GetNotifier. Мы его аккуратненько и довольно неявненько захватили через копию указателя this. До С++ 20 мы могли захватывать this вот так по значению и такую проблему будет очень сложно дебагать. Ситуация чуть улучшилась в С++20, мы теперь обязаны указывать
Так уже чуть проще отловить проблему.
Как это лечить? Если у вас объект класса провисает, то тут поможет только профилактика и рефакторинг.
В случае с захватом this профилактикой может быть синтаксическое ограничение использование методов, возвращающий лямбду, с помощью ref-квалификаторов методов:
Теперь вы не сможете вызвать этот метод на временном объекте, потому что удалена соответствующая перегрузка.
Конечно это вряд ли поможет в многопоточке, но это уже что-то.
Refer to actual things. Stay cool.
#cppcore #cpp11 #cpp20
#новичкам
Все знают, что возврат ссылки на локальный объект функции приводит к неопределенному поведению. Однако не всегда так просто можно распознать такие ситуации.
В C++11 появились лямбда-выражения, а вместе с ними ещё один способ прострелить себе причинное место.
Лямбда, захватывающая что-либо по ссылке, безопасна до тех пор, пока она не возвращается куда-либо за пределы области, в которой её создали. Как только лямбда покинула скоуп - можно начинать молиться:
auto make_add_n(int n) {
return [&](int x) {
return x + n; // n will become dangling reference!
};
}
auto add5 = make_add_n(5);
std::cout << add5(5) << std::endl; // UB!Гцц и шланг пишут разный результат на консоль, что напрямую говорит об ub. Можете посмотреть тут. На варнинги об этой ситуации лучше не надеяться, потому что гцц например думает, что в коде все в порядке.
Еще более интересная ситуация с объектами и методами.
struct Task {
int id;
std::function<void()> GetNotifier() {
return [=]{
std::cout << "notify " << id << std::endl;
};
}
};
int main() {
auto notify = Task { 5 }.GetNotifier();
notify();
}Что же здесь может провиснуть? Никаких локальных объектов в методе GetNotifier нет.
На самом деле провиснет сам объект, на котором вызывается GetNotifier. Мы его аккуратненько и довольно неявненько захватили через копию указателя this. До С++ 20 мы могли захватывать this вот так по значению и такую проблему будет очень сложно дебагать. Ситуация чуть улучшилась в С++20, мы теперь обязаны указывать
this в списке захвата:struct Task {
int id;
std::function<void()> GetNotifier() {
return [this]{
std::cout << "notify " << id << std::endl;
};
}
};Так уже чуть проще отловить проблему.
Как это лечить? Если у вас объект класса провисает, то тут поможет только профилактика и рефакторинг.
В случае с захватом this профилактикой может быть синтаксическое ограничение использование методов, возвращающий лямбду, с помощью ref-квалификаторов методов:
struct Task {
int id;
std::function<void()> GetNotifier() && = delete; // forbit call on temporaries
std::function<void()> GetNotifier() & {
return [this]{
std::cout << "notify " << id << std::endl;
};
}
};Теперь вы не сможете вызвать этот метод на временном объекте, потому что удалена соответствующая перегрузка.
Конечно это вряд ли поможет в многопоточке, но это уже что-то.
Refer to actual things. Stay cool.
#cppcore #cpp11 #cpp20
2🔥28👍19❤11🤯1
История capture this
#опытным
Проследим историю захвата this в лямбду сквозь стандарты С++, там есть на что посмотреть.
С++11
Появились лямбды и в них можно захватывать this как явно, так и неявно через параметры захвата по-умолчанию. Во всех случаях захватывается
Однако не было адекватного способа захватить объект по значению aka скопировать его в лямбду.
С++14
Появилась инициализация в захвате, поэтому стал реальным захват объекта по значению:
В остальном все осталось также.
С++17
Появился захват объекта по значению! Захват по значению может быть очень важно для асинхронных операций, которые откладывают выполнение лямбд:
В этом коде UB, потому что возвращаем лямбду со ссылкой на несуществующий объект. Поменять это можно захватом объекта по значению:
В этом случае в лямбде будет храниться копия объекта и все методы будут обращаться к этому скопированному объекту.
С++20
С появлением захвата this по значению стала очень путающей семантика неявного захвата. Что reference capture &, что value capture =, по факту захватывали текущий объект по ссылке. И ничто неявно не захватывало this по значению.
Изначально в 20-м стандарте эту проблему хотели решить, просто запретив неявный захват this в любом случае. Но посидели и поняли, что для ссылочного захвата по умолчанию семантика неявного захвата ссылки на объект(чем отдаленно явняется this) корректна. А вот для захвата по значению - нет.
Поэтому начиная с С++20 мы не можем неявно захватывать this в default capture by value:
Оставлю в картинке под постом инфографику по изменениям в стандартах относительно захвата this.
Know the history. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
#опытным
Проследим историю захвата this в лямбду сквозь стандарты С++, там есть на что посмотреть.
С++11
Появились лямбды и в них можно захватывать this как явно, так и неявно через параметры захвата по-умолчанию. Во всех случаях захватывается
&(*this) то есть указатель на текущий объект:struct Foo {
int m_x = 0;
void func() {
int x = 0;
//Explicit capture 'this'
[this]() { /*access m_x and x*/ }();
//Implcit capture 'this'
[&]() { /*access m_x and x*/ }();
//Redundant 'this'
[&, this]() { /*access m_x and x*/ }();
//Implcit capture 'this'
[=]() { /*access m_x and x*/ }();
//Error
[=, this]() { }();
}
};Однако не было адекватного способа захватить объект по значению aka скопировать его в лямбду.
С++14
Появилась инициализация в захвате, поэтому стал реальным захват объекта по значению:
struct Foo {
int m_x = 0;
void func() {
[copy=*this]() mutable {
copy.m_x++;
}();
}
};В остальном все осталось также.
С++17
Появился захват объекта по значению! Захват по значению может быть очень важно для асинхронных операций, которые откладывают выполнение лямбд:
struct Processor {
//Some state data..
std::future<void> process(/*args*/) {
//Pre-process...
//Do the data processing asynchronously
return
std::async(std::launch::async,
[=](/*data*/){
/*
Runs in a different thread.
'this' might be invalidated here
*/
//process data
});
}
};
auto caller() {
Processor p;
return p.process(/*args*/);
} В этом коде UB, потому что возвращаем лямбду со ссылкой на несуществующий объект. Поменять это можно захватом объекта по значению:
struct Processor {
std::future<void> process(/*args*/) {
return
std::async(std::launch::async,
[*this](/*data*/){
/*
Runs in a different thread.
'this' might be invalidated here
*/
//process data
});
}
};В этом случае в лямбде будет храниться копия объекта и все методы будут обращаться к этому скопированному объекту.
С++20
С появлением захвата this по значению стала очень путающей семантика неявного захвата. Что reference capture &, что value capture =, по факту захватывали текущий объект по ссылке. И ничто неявно не захватывало this по значению.
Изначально в 20-м стандарте эту проблему хотели решить, просто запретив неявный захват this в любом случае. Но посидели и поняли, что для ссылочного захвата по умолчанию семантика неявного захвата ссылки на объект(чем отдаленно явняется this) корректна. А вот для захвата по значению - нет.
Поэтому начиная с С++20 мы не можем неявно захватывать this в default capture by value:
struct Bagel {
int x = 0;
void func() {
//OK until C++20. Warning in C++20.
[=]() { std::cout << x; }();
//Error/warning until C++20. OK in C++20.
[=, this]() { std::cout << x; }();
}
};Оставлю в картинке под постом инфографику по изменениям в стандартах относительно захвата this.
Know the history. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
🔥16❤10👍10🤯6
starts_with, ends_with
#новичкам
До (и включая) C++17, если вы хотите проверить начало или конец в строке на соответствие референсу, вы должны использовать самописные решения, буст или другие сторонние библиотеки. К счастью, это меняется с C++20.
В нем появляются стандартные методы
Как видите, они имеют три перегрузки: для string_view, одного символа и строкового литерала.
Кейсов применения этих методов предостаточно: валидация расширения файла, валидация url, html кода, префикса пути до файла, хэдэров http реквестов. В любом более менее большом проекте найдется местечко для этих методов.
Примерчик:
Здесь мы простейшим отображением ranges отфильтровываем строки вектора, которые являются тегами, и оставляем только текстовую часть.
В общем, полезные штуки, которые помогают заменить громоздкие кастомные проверки через find на использование выразительных функций.
Be expressive. Stay cool.
#cpp20 #STL
#новичкам
До (и включая) C++17, если вы хотите проверить начало или конец в строке на соответствие референсу, вы должны использовать самописные решения, буст или другие сторонние библиотеки. К счастью, это меняется с C++20.
В нем появляются стандартные методы
std::string/std::string_view .starts_with() и .ends_with():constexpr bool starts_with(string_view sv) const noexcept;
constexpr bool starts_with(CharT c ) const noexcept;
constexpr bool starts_with(const CharT* s ) const;
constexpr bool ends_with(string_view sv )const noexcept;
constexpr bool ends_with(CharT c ) const noexcept;
constexpr bool ends_with(const CharT* s ) const;
Как видите, они имеют три перегрузки: для string_view, одного символа и строкового литерала.
const std::string url { "https://isocpp.org" };
// string literals
if (url.starts_with("https") && url.ends_with(".org"))
std::cout << "you're using the correct site!\n";
// a single char:
if (url.starts_with('h') && url.ends_with('g'))
std::cout << "letters matched!\n";Кейсов применения этих методов предостаточно: валидация расширения файла, валидация url, html кода, префикса пути до файла, хэдэров http реквестов. В любом более менее большом проекте найдется местечко для этих методов.
Примерчик:
const std::vector<std::string> tokens {
"<header>",
"<h1>",
"Hello World",
"</h1>",
"<p>",
"This is my super cool new web site.",
"</p>",
"<p>",
"Have a look and try!",
"</p>",
"</header>"
};
auto text = tokens |
std::views::filter([](const std::string& s) {
if (s.starts_with("<") || s.ends_with(">"))
return false;
return true;
});
for (const auto& str : text)
std::cout << str << std::endl;
// OUTPUT:
// Hello World
// This is my super cool new web site.
// Have a look and try!Здесь мы простейшим отображением ranges отфильтровываем строки вектора, которые являются тегами, и оставляем только текстовую часть.
В общем, полезные штуки, которые помогают заменить громоздкие кастомные проверки через find на использование выразительных функций.
Be expressive. Stay cool.
#cpp20 #STL
13🔥31❤12👍8❤🔥3🐳2
std::type_identity
#опытным
Не так давно мы разбирали функцию std::clamp, которая ограничивает значение переменной верхней и нижней границей:
В таком виде это прекрасно работает. Однако у std::clamp есть одна проблема: все три ее параметра должны быть одного типа:
Если попытаться использовать функцию с разными типами, то получим ошибку вывода типов:
Компилятор не поймет, какой тип Т имелся ввиду, потому что все три аргумента разных типов.
Можно было сделать 3 отдельных параметра:
Но тогда приходилось бы навешивать какие-то compile-time проверки совместимости типов.
Есть подход получше - использовать C++20 std::type_identity. Это максимально простая обертка над типом:
Но этот простой финт ушами дает очень важный эффект - отсутствие контекста вывода в шаблонах:
При использовании зависимых имен(type - зависимое имя шаблонного класса type_identity) компилятор не вывод тип Т для аргументов. Он либо полагается на явное указание аргументов при инстанциации, либо на вывод типа из других параметров. В последнем сниппете только параметр num находится в контексте вывода и по нему компилятор выводит тип Т. Типы параметров low и high не зависят от того, какие соответствующие аргументы мы передаем при вызове функции. Они определяются выведенным типом первого аргумента.
В данном случае тип num выведется в double, поэтому и типы low и high тоже будут double. При вызове просто сработает неявное преобразование от int к double.
Также type_identity можно использовать для того, чтобы запретить вывод типов и заставить пользователя явно прописывать шаблонные параметры. Это может быть важно для точной передачи типа:
Тоже самое для вариадиков:
Прикольный инструмент для тонкой настройки вашего шаблонного кода.
Спасибо @d7d1cd за идею для поста)
Turn off deduction when it is not needed. Stay cool.
#template #cpp20
#опытным
Не так давно мы разбирали функцию std::clamp, которая ограничивает значение переменной верхней и нижней границей:
double increment_speed(double curr_speed, double acceleration, double time_delta) {
curr_speed += acceleration * time_delta;
return std::clamp(curr_speed, kMinSpeed, kMaxSpeed);
}В таком виде это прекрасно работает. Однако у std::clamp есть одна проблема: все три ее параметра должны быть одного типа:
template<class T>
constexpr const T& clamp( const T& v, const T& lo, const T& hi );
Если попытаться использовать функцию с разными типами, то получим ошибку вывода типов:
auto bounded = std::clamp(42, 3.14, 69.f); // ERROR!
Компилятор не поймет, какой тип Т имелся ввиду, потому что все три аргумента разных типов.
Можно было сделать 3 отдельных параметра:
template<class T1, class T2, class T3>
constexpr const T& clamp( const T1& v, const T2& lo, const T3& hi );
Но тогда приходилось бы навешивать какие-то compile-time проверки совместимости типов.
Есть подход получше - использовать C++20 std::type_identity. Это максимально простая обертка над типом:
template<class T>
struct type_identity { using type = T; };
template< class T >
using type_identity_t = type_identity<T>::type;
Но этот простой финт ушами дает очень важный эффект - отсутствие контекста вывода в шаблонах:
template <class T>
auto bound(T num, typename std::type_identity<T>::type low, typename std::type_identity<T>::type high) {
return std::clamp(num, low, high);
}
auto bounded = bound(25.5, 20, 25);
При использовании зависимых имен(type - зависимое имя шаблонного класса type_identity) компилятор не вывод тип Т для аргументов. Он либо полагается на явное указание аргументов при инстанциации, либо на вывод типа из других параметров. В последнем сниппете только параметр num находится в контексте вывода и по нему компилятор выводит тип Т. Типы параметров low и high не зависят от того, какие соответствующие аргументы мы передаем при вызове функции. Они определяются выведенным типом первого аргумента.
В данном случае тип num выведется в double, поэтому и типы low и high тоже будут double. При вызове просто сработает неявное преобразование от int к double.
Также type_identity можно использовать для того, чтобы запретить вывод типов и заставить пользователя явно прописывать шаблонные параметры. Это может быть важно для точной передачи типа:
template<class T>
void foo(typename std::type_identity<T>::type arg) {}
foo<int>(42); // T жёстко задаётся как int
// foo(42); // Ошибка: вывод T невозможен!
Тоже самое для вариадиков:
template <typename... Ts>
void process(typename std::type_identity<std::tuple<Ts...>>::type data) {
}
process<int, double>(std::tuple{1, 2.0}); // OK
process(std::tuple{1, 2.0}); // ERROR, не указаны типы шаблонных параметров
Прикольный инструмент для тонкой настройки вашего шаблонного кода.
Спасибо @d7d1cd за идею для поста)
Turn off deduction when it is not needed. Stay cool.
#template #cpp20
9❤29👍19🔥9🤯2
Как итерироваться в обратном порядке?
#новичкам
Кто часто решал задачки на литкоде поймут проблему. Есть вектор и надо проитерироваться по нему с конца. Ну пишем:
В чем проблема этого кода?
Бесконечный цикл и ub. auto определяет тип i беззнаковым, который физически не может быть меньше нуля. Происходит переполнение, i становится очень большим и происходит доступ к невалидной памяти.
В большинстве задач можно написать тип int и все будет работать. Но все-таки size() возвращает size_t и будет происходить сужающее преобразование. В реальных проектах нужно избегать этого и сегодня мы посмотрим, как безопасно итерироваться в обратном порядке.
✅ Использовать свободную функцию ssize() из C++20:
Ее можно применить к вектору и она вернет значение типа std::ptrdiff_t. В первом приблежении это знаковый аналог std::size_t, который позволяет вычислять расстояние между двумя указателями, даже для очень больших массивов.
Так как тип знаковый и в большинстве реализаций его размер сопоставим с size_t, то можно не переживать по поводу возможной срезки длины вектора до меньшего типа.
✅ Использовать обратные итераторы:
Тут все довольно очевидно и безопасно.
Однако cppcore гайдлайны говорят нам, что нужно предпочитать использовать range-based-for циклы обычным for'ам. Чтож, давайте пойдем в эту сторону.
✅ Написать свой легковесный адаптер для итерирования в обратном порядке:
Делаем тонкую обертку над любым итерируемым объектом(в рабочем коде нужно всяких концептов навесить, чтобы было прям по-красоте) и элегантно итерируемся по контейнеру.
✅ А ренджи для кого придумали? Они для этой задачи подходят идеально:
Рэнджи из C++20 предоставляют кучу удобных адаптеров для работы с контейнерами. В сущности std::views::reverse или std::ranges::reverse_view делает примерно то же самое, что и мы сами написали в третьем пункте.
Можно совсем упороться и применить алгоритмы ренждей:
Бывает, что индексы элементов все-таки нужны внутри цикла. Но это решается с помощью std::ranges::iota_view. Оставляем реализацию этого решения для домашних изысканий.
Have a large toolkit. Stay cool.
#cppcore #cpp20 #STL
#новичкам
Кто часто решал задачки на литкоде поймут проблему. Есть вектор и надо проитерироваться по нему с конца. Ну пишем:
std::vector vec { 1, 2, 3, 4, 5, 6 };
for (auto i = vec.size() - 1; i >= 0; --i) {
std::cout << i << ": " << vec[i] << '\n';
}В чем проблема этого кода?
Бесконечный цикл и ub. auto определяет тип i беззнаковым, который физически не может быть меньше нуля. Происходит переполнение, i становится очень большим и происходит доступ к невалидной памяти.
В большинстве задач можно написать тип int и все будет работать. Но все-таки size() возвращает size_t и будет происходить сужающее преобразование. В реальных проектах нужно избегать этого и сегодня мы посмотрим, как безопасно итерироваться в обратном порядке.
✅ Использовать свободную функцию ssize() из C++20:
std::vector vec { 1, 2, 3, 4, 5, 6 };
for (auto i = std::ssize(vec) - 1; i >= 0; --i) {
std::cout << vec[i] << '\n';
}Ее можно применить к вектору и она вернет значение типа std::ptrdiff_t. В первом приблежении это знаковый аналог std::size_t, который позволяет вычислять расстояние между двумя указателями, даже для очень больших массивов.
Так как тип знаковый и в большинстве реализаций его размер сопоставим с size_t, то можно не переживать по поводу возможной срезки длины вектора до меньшего типа.
✅ Использовать обратные итераторы:
std::vector vec { 1, 2, 3, 4, 5, 6 };
for (auto it = std::rbegin(vec); it != std::rend(vec); ++it)
std::cout << *it << '\n';Тут все довольно очевидно и безопасно.
Однако cppcore гайдлайны говорят нам, что нужно предпочитать использовать range-based-for циклы обычным for'ам. Чтож, давайте пойдем в эту сторону.
✅ Написать свой легковесный адаптер для итерирования в обратном порядке:
template <typename T>
class reverse {
private:
T &iterable_;
public:
explicit reverse(T &iterable) : iterable_{iterable} {}
auto begin() const { return std::rbegin(iterable_); }
auto end() const { return std::rend(iterable_); }
};
std::vector vec{1, 2, 3, 4, 5};
for (const auto &elem : reverse(vec))
std::cout << elem << '\n';
Делаем тонкую обертку над любым итерируемым объектом(в рабочем коде нужно всяких концептов навесить, чтобы было прям по-красоте) и элегантно итерируемся по контейнеру.
✅ А ренджи для кого придумали? Они для этой задачи подходят идеально:
for (const auto& elem : vec | std::views::reverse)
std::cout << elem << '\n';
// или без пайпов
for (const auto& elem : std::ranges::reverse_view(vec))
std::cout << elem << '\n';
Рэнджи из C++20 предоставляют кучу удобных адаптеров для работы с контейнерами. В сущности std::views::reverse или std::ranges::reverse_view делает примерно то же самое, что и мы сами написали в третьем пункте.
Можно совсем упороться и применить алгоритмы ренждей:
std::ranges::copy(vec | std::views::reverse,
std::ostream_iterator<int>( std::cout,"\n" ));
// или c лямбдой
std::ranges::for_each(vec | std::views::reverse,
[](const auto& elem) {
std::cout << elem << '\n';
});
Бывает, что индексы элементов все-таки нужны внутри цикла. Но это решается с помощью std::ranges::iota_view. Оставляем реализацию этого решения для домашних изысканий.
Have a large toolkit. Stay cool.
#cppcore #cpp20 #STL
❤36👍23🔥11👎3👀2❤🔥1
Оператор, бороздящий просторы вселенной
#новичкам
В этом посте мы рассказали об одной фишке, которая может помочь при сравнении кастомных структур:
Однако иногда структуры требуется сравнивать и с помощью других операторов: >, ==, !=, >=, <=. В итоге полноценный набор операторов сравнения для Time выглядит так:
Попахивает зловонным бойлерплейтом.
Недавно увидел мем, где девочка 8-ми лет, которая изучает питон, спрашивает отца: "папа, а если компьютер знает, что здесь пропущено двоеточие, почему он сам не может его поставить?". И батя такой: "Я не знаю, дочка, я не знаю ...".
Здесь вот похожая ситуация. Компилятор же умеет сравнивать набор чисел в лексикографическом порядке. Какого хрена он не может сделать это за нас?
Начиная с С++20 может!
Теперь вы можете сказать компилятору, что вам достаточно простого лексикографического сравнения поле класса и пусть он сам его генерирует:
В отличие от специальных методов класса, компилятор не сгенерирует за нас эти операторы, если мы явно не попросим. Получается, что мы решили только полпроблемы и нам все равно нужно писать 6 скучных засоряющих код строчек. Хотелось бы один раз сказать, что нам нужны сразу все операторы.
Тут же нам на помощью приходит еще одна фича С++20 - трехсторонний оператор сравнения или spaceship operator. Теперь код выглядит так:
Spaceship потому что похож на космический корабль, имхо прям имперский истребитель из далекой-далекой.
Один раз определив этот оператор можно сравнивать объекты какими угодно операторами и это будет работать. Подробнее про применение будет в следующем посте.
Conquer your space. Stay cool.
#cppcore #cpp20
#новичкам
В этом посте мы рассказали об одной фишке, которая может помочь при сравнении кастомных структур:
struct Time {
int hours;
int minutes;
int seconds;
bool operator<(const Time& other) {
return std::tie(hours, minutes, seconds) < std::tie(other.hours, other.minutes, other.seconds);
}
};Однако иногда структуры требуется сравнивать и с помощью других операторов: >, ==, !=, >=, <=. В итоге полноценный набор операторов сравнения для Time выглядит так:
struct Time {
int hours;
int minutes;
int seconds;
bool operator<(const Time& other) const noexcept {
return std::tie(hours, minutes, seconds) < std::tie(other.hours, other.minutes, other.seconds);
}
bool operator==(const Time& other) const noexcept {
return std::tie(hours, minutes, seconds) == std::tie(other.hours, other.minutes, other.seconds);
}
bool operator<=(const Time& other) const noexcept { return !(other < *this); }
bool operator>(const Time& other) const noexcept { return other < *this; }
bool operator>=(const Time& other) const noexcept { return !(*this < other); }
bool operator!=(const Time& other) const noexcept { return !(*this == other); }
};Попахивает зловонным бойлерплейтом.
Недавно увидел мем, где девочка 8-ми лет, которая изучает питон, спрашивает отца: "папа, а если компьютер знает, что здесь пропущено двоеточие, почему он сам не может его поставить?". И батя такой: "Я не знаю, дочка, я не знаю ...".
Здесь вот похожая ситуация. Компилятор же умеет сравнивать набор чисел в лексикографическом порядке. Какого хрена он не может сделать это за нас?
Начиная с С++20 может!
Теперь вы можете сказать компилятору, что вам достаточно простого лексикографического сравнения поле класса и пусть он сам его генерирует:
struct Time {
int hours;
int minutes;
int seconds;
bool operator<(const Time& other) const = default;
bool operator==(const Time& other) const = default;
bool operator<=(const Time& other) const = default;
bool operator>(const Time& other) const = default;
bool operator>=(const Time& other) const = default;
bool operator!=(const Time& other) const = default;
};В отличие от специальных методов класса, компилятор не сгенерирует за нас эти операторы, если мы явно не попросим. Получается, что мы решили только полпроблемы и нам все равно нужно писать 6 скучных засоряющих код строчек. Хотелось бы один раз сказать, что нам нужны сразу все операторы.
Тут же нам на помощью приходит еще одна фича С++20 - трехсторонний оператор сравнения или spaceship operator. Теперь код выглядит так:
struct Time {
int hours;
int minutes;
int seconds;
// Один оператор вместо шести!
auto operator<=>(const Time& other) const = default;
};Spaceship потому что похож на космический корабль, имхо прям имперский истребитель из далекой-далекой.
Один раз определив этот оператор можно сравнивать объекты какими угодно операторами и это будет работать. Подробнее про применение будет в следующем посте.
Conquer your space. Stay cool.
#cppcore #cpp20
❤40👍22🔥12
Spaceship оператор. Детали 1
#новичкам
В прошлом посте мы рассказали, как трехсторонний оператор сравнения может помочь сократить код определения операций сравнения, но это не единственное его предназначение. Сегодня подробнее рассмотрим, какую функциональность он предоставляет.
Ну для начала: наличие определенного spaceship оператора гарантирует вам наличие всех 6 операций сравнения:
Это уже прекрасно, но это еще не все!
Обратите внимание на сигнатуру spaceship operator. Зачем там нужен auto?
Вот теперь объясненяем, почему это называется оператор трехстороннего сравнения.
Он возвращает объект, который содержит информацию о результате сравнения:
Если результат сравнения >0, то первый операнд больше второго. И так далее по аналогии.
Тип возвращаемого значения у оператора один из этих трех:
- std::strong_ordering
- std::weak_ordering
- std::partial_ordering
Что они значат - тема отдельного разговора, но каждый из них может находится в одном из 3-х состояний: less, greater, equal. Это можно использовать, например, для проверки возвращаемых значений системных вызовов:
Кейсы применения непосредственно spaceship'а в коде не так обширны, потому что не очень привычно, есть вопросы к перфу(об этом в следующем посте) да и поди разберись с этими ордерингами еще. Но его точно стоит использовать для автоматической генерации 6 базовых операторов.
Be universal. Stay cool.
#cppcore #cpp20
#новичкам
В прошлом посте мы рассказали, как трехсторонний оператор сравнения может помочь сократить код определения операций сравнения, но это не единственное его предназначение. Сегодня подробнее рассмотрим, какую функциональность он предоставляет.
Ну для начала: наличие определенного spaceship оператора гарантирует вам наличие всех 6 операций сравнения:
struct Time {
int hours;
int minutes;
int seconds;
// Spaceship operator (генерирует все 6 операторов сравнения)
auto operator<=>(const Time& other) const = default;
};
Time t1{10, 30, 15}; // 10:30:15
Time t2{9, 45, 30}; // 09:45:30
Time t3{10, 30, 15}; // 10:30:15
assert(t1 > t2); // 10:30:15 > 09:45:30
assert(!(t1 < t2)); // 10:30:15 не < 09:45:30
assert(t1 == t3); // 10:30:15 == 10:30:15
assert(t1 != t2); // 10:30:15 != 09:45:30
assert(t1 <= t3); // 10:30:15 <= 10:30:15
assert(t1 >= t2); // 10:30:15 >= 09:45:30Это уже прекрасно, но это еще не все!
Обратите внимание на сигнатуру spaceship operator. Зачем там нужен auto?
Вот теперь объясненяем, почему это называется оператор трехстороннего сравнения.
Он возвращает объект, который содержит информацию о результате сравнения:
Time t1{10, 30, 15}; // 10:30:15
Time t2{9, 45, 30}; // 09:45:30
// Можно использовать и сам spaceship operator напрямую
auto cmp = t1 <=> t2;
if (cmp > 0) {
std::cout << "t1 is later than t2\n";
} else if (cmp < 0) {
std::cout << "t1 is earlier than t2\n";
} else {
std::cout << "t1 is the same as t2\n";
}
// OUTPUT:
// t1 is later than t2Если результат сравнения >0, то первый операнд больше второго. И так далее по аналогии.
Тип возвращаемого значения у оператора один из этих трех:
- std::strong_ordering
- std::weak_ordering
- std::partial_ordering
Что они значат - тема отдельного разговора, но каждый из них может находится в одном из 3-х состояний: less, greater, equal. Это можно использовать, например, для проверки возвращаемых значений системных вызовов:
constexpr int strong_ordering_to_int(const std::strong_ordering& o)
{
if (o == std::strong_ordering::less) return -1;
if (o == std::strong_ordering::greater) return 1;
return 0;
}
char buffer[256];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
// Сравниваем результат read() с нулём через <=>
switch (strong_ordering_to_int(bytes_read <=> 0)) {
case 1:
std::cout << "Read " << bytes_read << " bytes: "
<< std::string(buffer, bytes_read) << "\n";
break;
case 0:
std::cout << "End of file reached (0 bytes read)\n";
break;
case -1:
perror("read failed");
return 1;
}
Кейсы применения непосредственно spaceship'а в коде не так обширны, потому что не очень привычно, есть вопросы к перфу(об этом в следующем посте) да и поди разберись с этими ордерингами еще. Но его точно стоит использовать для автоматической генерации 6 базовых операторов.
Be universal. Stay cool.
#cppcore #cpp20
❤30👍15🔥10⚡1
Какой день будет через месяц?
#новичкам
Работа со временем в стандартных плюсах - боль. Долгое время ее вообще не было. chrono появилась так-то в С++11. Но и даже с ее появлением жить стало лишь немногим легче.
Например, простая задача: "Прибавить к текущей дате 1 месяц".
В С++11 у нас есть только часы и точки на временной линии. Тут просто дату-то получить сложно. Есть конечно сишная std::localtime, можно мапулировать отдельными полями std::tm(днями, минутами и тд), но придется конвертировать сишные структуры времени в плюсовые, да и можно нарваться на трудноотловимые ошибки, если попытаться увеличить на 1 месяц 30 января.
Как прибавить к дате месяц? +30 дней или +1 месяц не канает. А если февраль? А если високосный год?
В общем стандартного решения нет... Или есть?
В С++20 в библиотеку chrono завезли кучу полезностей. А в частности функционал календаря. Теперь мы можем манипулировать отдельно датами и безопасно их изменять.
Например, чтобы получить сегодняшнюю дату и красиво ее вывести на консоль достаточно сделать следующее:
Появился прекрасный класс std::chrono::year_month_day, который отражает конкретно дату. И его объекты замечательно сериализуются в поток.
Если вам нужно задать определенный формат отображения - не проблема! Есть std::format:
С помощью std::chrono::year_month_day можно удобно манипулировать датами и, главное, делать это безопасно. Что будет если я к 29 января прибавлю месяц?
А будет все хорошо, если год високосный. Но если нет, то библиотека нам явно об этом скажет.
В общем, крутой фиче-сет, сильно облегчает работу со стандартными временными точками.
Take your time. Stay cool.
#cpp11 #cpp20
#новичкам
Работа со временем в стандартных плюсах - боль. Долгое время ее вообще не было. chrono появилась так-то в С++11. Но и даже с ее появлением жить стало лишь немногим легче.
Например, простая задача: "Прибавить к текущей дате 1 месяц".
В С++11 у нас есть только часы и точки на временной линии. Тут просто дату-то получить сложно. Есть конечно сишная std::localtime, можно мапулировать отдельными полями std::tm(днями, минутами и тд), но придется конвертировать сишные структуры времени в плюсовые, да и можно нарваться на трудноотловимые ошибки, если попытаться увеличить на 1 месяц 30 января.
Как прибавить к дате месяц? +30 дней или +1 месяц не канает. А если февраль? А если високосный год?
В общем стандартного решения нет... Или есть?
В С++20 в библиотеку chrono завезли кучу полезностей. А в частности функционал календаря. Теперь мы можем манипулировать отдельно датами и безопасно их изменять.
Например, чтобы получить сегодняшнюю дату и красиво ее вывести на консоль достаточно сделать следующее:
std::chrono::year_month_day current_date =
std::chrono::floor<std::chrono::days>(
std::chrono::system_clock::now());
std::cout << "Today is: " << current_date << '\n';
// Today is: 2025-09-10
Появился прекрасный класс std::chrono::year_month_day, который отражает конкретно дату. И его объекты замечательно сериализуются в поток.
Если вам нужно задать определенный формат отображения - не проблема! Есть std::format:
std::cout << "Custom: "
<< std::format("{:%d.%m.%Y}", current_date)
<< '\n';
// Custom: 10.09.2025
С помощью std::chrono::year_month_day можно удобно манипулировать датами и, главное, делать это безопасно. Что будет если я к 29 января прибавлю месяц?
auto date = std::chrono::year_month_day{
std::chrono::year(2004), std::chrono::month(1), std::chrono::day(29)};
std::cout << "Date: " << date << "\n";
std::chrono::year_month_day next_month = date + std::chrono::months{1};
std::chrono::year_month_day next_year_plus_month =
date + std::chrono::years{1} + std::chrono::months{1};
std::cout << "Next month: " << next_month << "\n";
std::cout << "Next year plus month: " << next_year_plus_month << "\n";
// OUTPUT:
// Date: 2004-01-29
// Next month: 2004-02-29
// Next year plus month: 2005-02-29 is not a valid dateА будет все хорошо, если год високосный. Но если нет, то библиотека нам явно об этом скажет.
В общем, крутой фиче-сет, сильно облегчает работу со стандартными временными точками.
Take your time. Stay cool.
#cpp11 #cpp20
❤22👍17🔥6😁3
Сколько времени сейчас в Москве?
#новичкам
Как в стандартных плюсах работать с временными зонами? Да никак до С++20. Приходилось использовать разные сторонние решения.
Но стандарт развивается и у нас теперь есть возможность работать с зонами в чистом С++!
Появился класс std::chrono::zoned_time, который представляет собой пару из временной метки и временной зоны. Создать зонированное время можно так:
Функция std::chrono::current_zone() позволяет получить локальную временную зону.
Можно также передать имя зоны:
И это все прекрасно работает с std::format, который позволяет информацию о временной точки настолько подробно, насколько это возможно:
Работы с временными зонами очень не хватало в стандарте и круто, что ее добавили.
Develop yourself. Stay cool.
#cpp20
#новичкам
Как в стандартных плюсах работать с временными зонами? Да никак до С++20. Приходилось использовать разные сторонние решения.
Но стандарт развивается и у нас теперь есть возможность работать с зонами в чистом С++!
Появился класс std::chrono::zoned_time, который представляет собой пару из временной метки и временной зоны. Создать зонированное время можно так:
auto now = std::chrono::zoned_time{std::chrono::current_zone(), std::chrono::system_clock::now()};Функция std::chrono::current_zone() позволяет получить локальную временную зону.
Можно также передать имя зоны:
auto msw_time = std::chrono::zoned_time{"Europe/Moscow", std::chrono::system_clock::now()};И это все прекрасно работает с std::format, который позволяет информацию о временной точки настолько подробно, насколько это возможно:
std::string get_time_string(const std::chrono::zoned_time<std::chrono::system_clock::duration>& zt) {
return std::format("{:%Y-%m-%d %H:%M:%S %Z}", zt);
}
std::string get_detailed_time_string(const std::chrono::zoned_time<std::chrono::system_clock::duration>& zt) {
return std::format("{:%A, %d %B %Y, %H:%M:%S %Z (UTC%z)}", zt);
}
std::cout << "Current time: " << get_time_string(now) << std::endl;
std::cout << "Detailed: " << get_detailed_time_string(now) << std::endl;
std::cout << "Time in Moscow: " << get_time_string(msw_time) << std::endl;
std::cout << "Detailed: " << get_detailed_time_string(msw_time) << std::endl;
// OUTPUT:
// Current time: 2025-09-11 17:50:48.035852842 UTC
// Detailed: Thursday, 11 September 2025, 17:50:48.035852842 UTC (UTC+0000)
// Time in Moscow: 2025-09-11 20:50:48.041000112 MSK
// Detailed: Thursday, 11 September 2025, 20:50:48.041000112 MSK (UTC+0300)Работы с временными зонами очень не хватало в стандарте и круто, что ее добавили.
Develop yourself. Stay cool.
#cpp20
2🔥36👍12❤8😁7
Уплощаем многомерный массив
#опытным
Иногда у вас есть коллекция элементов, для каждого из которых вы выполняете операцию, возвращающую вектор значений:
Итоговое отображение result_view - это по факту набор векторов. Чтобы сложить это все в один массив нужен двойной цикл. А можно как-то удобно и лаконично получить плоский вектор интов?
С помощью С++20 отображения std::views::join:
Это все сработает и на экране появлятся заветные чиселки.
Здесь используется std::ranges::to и std::print, которые добавлены в 23-м стандарте
Если у вас элементы, которые хотелось бы переместить, а не скопировать, то можно добавить еще с++23 отображение as_rvalue:
Если хочется чистого кода без циклов, то рэнджи для этого и сделаны.
Don't stuck in a loop. Stay cool.
#cpp20 #cpp23
#опытным
Иногда у вас есть коллекция элементов, для каждого из которых вы выполняете операцию, возвращающую вектор значений:
std::vector<int> Process(const std::string& str);
std::vector<std::string> elems = ...;
auto result_view = elems | std::views::transform([](const std::string& str) {
return Process(str);
})
Итоговое отображение result_view - это по факту набор векторов. Чтобы сложить это все в один массив нужен двойной цикл. А можно как-то удобно и лаконично получить плоский вектор интов?
С помощью С++20 отображения std::views::join:
std::vector<int> Process(const std::string& str);
std::vector<std::string> elems = ...;
auto result = elems | std::views::transform([](const std::string &str) {
return Process(str);
}) |
std::views::join | std::ranges::to<std::vector>();
std::print("{}", result);
Это все сработает и на экране появлятся заветные чиселки.
Здесь используется std::ranges::to и std::print, которые добавлены в 23-м стандарте
Если у вас элементы, которые хотелось бы переместить, а не скопировать, то можно добавить еще с++23 отображение as_rvalue:
auto result = elems | std::views::transform([](const auto & elem) {
return Process(elem);
}) |
std::views::join | std::views::as_rvalue |
std::ranges::to<std::vector>();Если хочется чистого кода без циклов, то рэнджи для этого и сделаны.
Don't stuck in a loop. Stay cool.
#cpp20 #cpp23
❤20👍13🔥7