Задачка для собеса. Пусть дан вектор беззнаковых целых, в которых есть нули. Нужно сдвинуть все нули в конец вектора, то есть написать код функции, используя стандартные алгоритмы:
Конечно, можно написать код самому, но так вы не покажете знания std. Как вариант, можно сделать сортировку в обратном порядке:
но тогда порядок нарушится и плюс это занимает O(N·log(N)).
Другим более интересным вариантом будет использование std::remove:
Дело в том что remove (в отличие от std::erase) не удаляет физически элементы из контейнера (про это уже было писано на канале). Вместо этого, нужные элементы сдвигаются в начало контейнера, а в конце остается "мусор". Нам остается только забить нулями "мусор", начало которого remove и вернет.
#include <iostream>
#include <vector>
#include <algorithm>
void move_zeroes_to_end(std::vector<size_t>& v)
{
// TODO
}
int main()
{
std::vector<size_t> v = { 0, 4, 8, 3, 7, 0, 1, 4, 4, 0 };
move_zeroes_to_end(v);
for (auto i : v) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
Конечно, можно написать код самому, но так вы не покажете знания std. Как вариант, можно сделать сортировку в обратном порядке:
std::sort(v.rbegin(), v.rend());но тогда порядок нарушится и плюс это занимает O(N·log(N)).
Другим более интересным вариантом будет использование std::remove:
auto zero_it = std::remove(v.begin(), v.end(), 0);
std::fill(zero_it, v.end(), 0);
Дело в том что remove (в отличие от std::erase) не удаляет физически элементы из контейнера (про это уже было писано на канале). Вместо этого, нужные элементы сдвигаются в начало контейнера, а в конце остается "мусор". Нам остается только забить нулями "мусор", начало которого remove и вернет.
👍3❤1
Оператор присвоения копии получает аргумент того же типа, что и класс.
Для совместимости с оператором присвоения встроенных типов операторы присвоения обычно возвращают (неконстантную) ссылку на свой левый операнд:
Как видим, благодаря возврату ссылки, работает множественное присваивание:
Однако, оператор присвоения копии может возвращать любое значение.
Если мы захотим возвращать ничего, то это тоже возможно:
В этом случае, множественное присваивание не сработает, однако единичное вполне:
Мы также можем (например по ошибке) вернуть не ссылку, а сам объект, а также например строку:
Конечно, так делать не рекомендуется.
Кстати, компилятор будет синтезировать оператор присвоения копии с возвратом неконстантной (кто знает почему?) ссылки на левый операнд.
Для совместимости с оператором присвоения встроенных типов операторы присвоения обычно возвращают (неконстантную) ссылку на свой левый операнд:
#include <iostream>
class Data
{
public:
Data(int i = 0) : _i(i) {}
Data& operator=(const Data & other) // copy assignment
{
std::cout << "operator= " << _i << std::endl;
_i = other._i;
return *this;
}
int i() const { return _i; }
private:
int _i;
};
int main() {
Data d1(1);
Data d2(2);
Data d3(3);
d1 = d2 = d3;
std::cout << d1.i() << d2.i() << d3.i() << std::endl;
return 0;
}
// Output:
// operator= 2
// operator= 1
// 333
Как видим, благодаря возврату ссылки, работает множественное присваивание:
d1 = d2 = d3;.Однако, оператор присвоения копии может возвращать любое значение.
Если мы захотим возвращать ничего, то это тоже возможно:
void operator=(const Data & other)В этом случае, множественное присваивание не сработает, однако единичное вполне:
d1 = d2 = d3; // ошибка!
d1 = d2; // ок
Мы также можем (например по ошибке) вернуть не ссылку, а сам объект, а также например строку:
#include <iostream>
#include <string>
class Data
{
public:
Data(int i = 0) : _i(i) {}
std::string operator=(const Data & other) // copy assignment
{
std::cout << "operator= " << _i << std::endl;
_i = other._i;
return "lovite narkomana";
}
int i() const { return _i; }
private:
int _i;
};
int main() {
Data d1(1);
Data d2(2);
std::cout << (d1 = d2) << std::endl;
return 0;
}
// Output:
// operator= 1
// lovite narkomana
Конечно, так делать не рекомендуется.
Кстати, компилятор будет синтезировать оператор присвоения копии с возвратом неконстантной (кто знает почему?) ссылки на левый операнд.
🤔1
Type traits часть 4 (перевод)
std::enable_if и SFINAE
Трейт std::enable_if принимает два параметра - булево значение и тип. Если значение булева параметра является истинным, то результирующая структура (enable_if это параметризированная структура) содержит поле
Представим такой код:
Во время компиляции, можно представить что наш enable_if код выльется просто в
Обычно мы используем алиас std::enable_if_t, который дает прямой доступ к
Давайте посмотрим, что будет если булево значение ложно:
В этом случае можно представить, что код эквивалентен такому:
Этот код невалиден и мы получим ошибку компиляции. И какая от всего этого польза? Она связана с правилом из мира шаблонов, называемого SFINAE (substitution failure is not an error - «неудавшаяся подстановка — не ошибка»). В общем, правило гласит: Если не получается рассчитать окончательные типы шаблонных параметров функции, компилятор не выбрасывает ошибку. Этот шаблон просто игнорируется и компилятор переходит к другим кандидатам.
Мы можем создать шаблонные функции, которые в зависимости от условий могут привести к ошибке подстановки. Например:
Если T это класс, то наша подставка будет выглядеть как-то так:
Код выше вполне себе валидный, так что наша функция будет способна обработать наш вызов. Однако, если T это не класс, то шаблон после подстановки будет выглядеть вот так:
В этот раз получившийся код не является валидным. Но, согласно правилу SFINAE, ошибка подстановки не обязательно значит ошибку компиляции. Ведь может быть другой шаблон, который подойдет.
В примере ниже, у нас есть шаблон, который приведет к ошибке подстановки, если тип является классом и другой, если тип не является классом.
Эта программа успешно скомпилится и выполнится, потому что все вызовы функций были покрыты шаблонами, которые не пострадали от ошибки подстановки.
Если код выше выглядит непонятным - то это так. Минимум - это запомнить, что такое правило есть и понять в чем его суть.
Стоит отметить, что в C++20 для упрощения темы шаблонного программирования были добавлены концепты.
std::enable_if и SFINAE
Трейт std::enable_if принимает два параметра - булево значение и тип. Если значение булева параметра является истинным, то результирующая структура (enable_if это параметризированная структура) содержит поле
type с типом второго параметра.Представим такой код:
#include <type_traits>
int main() {
std::enable_if<true, int>::type MyInt{42};
}
Во время компиляции, можно представить что наш enable_if код выльется просто в
int, что сделает наш код эквивалентным:#include <type_traits>
int main() {
int MyInt{42};
}
Обычно мы используем алиас std::enable_if_t, который дает прямой доступ к
type:std::enable_if_t<true, int> MyInt{42};Давайте посмотрим, что будет если булево значение ложно:
#include <type_traits>
int main() {
std::enable_if<false, int>::type MyInt{42};
}
В этом случае можно представить, что код эквивалентен такому:
#include <type_traits>
int main() {
MyInt{42};
}
Этот код невалиден и мы получим ошибку компиляции. И какая от всего этого польза? Она связана с правилом из мира шаблонов, называемого SFINAE (substitution failure is not an error - «неудавшаяся подстановка — не ошибка»). В общем, правило гласит: Если не получается рассчитать окончательные типы шаблонных параметров функции, компилятор не выбрасывает ошибку. Этот шаблон просто игнорируется и компилятор переходит к другим кандидатам.
Мы можем создать шаблонные функции, которые в зависимости от условий могут привести к ошибке подстановки. Например:
template <typename T,
std::enable_if_t<std::is_class_v<T>, int> = 0>
void Function(T Arg) {}
Если T это класс, то наша подставка будет выглядеть как-то так:
template <typename T, int = 0>
void Function(T Arg) {}
Код выше вполне себе валидный, так что наша функция будет способна обработать наш вызов. Однако, если T это не класс, то шаблон после подстановки будет выглядеть вот так:
template <typename T, = 0>
void Function(T Arg) {}
В этот раз получившийся код не является валидным. Но, согласно правилу SFINAE, ошибка подстановки не обязательно значит ошибку компиляции. Ведь может быть другой шаблон, который подойдет.
В примере ниже, у нас есть шаблон, который приведет к ошибке подстановки, если тип является классом и другой, если тип не является классом.
#include <type_traits>
#include <iostream>
template <typename T,
std::enable_if_t<std::is_class_v<T>, int> = 0>
void Function(T Arg) {
std::cout << "That was a class\n";
}
template <typename T,
std::enable_if_t<!std::is_class_v<T>, int> = 0>
void Function(T Arg) {
std::cout << "That wasn't\n";
}
class Character {};
int main() {
Function(Character{});
Function(3.14);
}
// Output:
// That was a class
// That wasn't
Эта программа успешно скомпилится и выполнится, потому что все вызовы функций были покрыты шаблонами, которые не пострадали от ошибки подстановки.
Если код выше выглядит непонятным - то это так. Минимум - это запомнить, что такое правило есть и понять в чем его суть.
Стоит отметить, что в C++20 для упрощения темы шаблонного программирования были добавлены концепты.
👍1
Исходники генератора подземелий для вашей визитки 😁.
https://gist.github.com/munificent/b1bcd969063da3e6c298be070a22b604
#юмор
https://gist.github.com/munificent/b1bcd969063da3e6c298be070a22b604
#юмор
❤🔥1😁1
Type traits часть 5 (перевод)
Условный выбор типа с помощью std::conditional
В заголовке
Несколько примеров:
Обычно,
Условный выбор типа с помощью std::conditional
В заголовке
<type_traits> есть шаблон std::conditional, который позволяет нам динамически выбирать тип на этапе компиляции. Он принимает три шаблонных параметра - булево значение, тип для использования, если первый параметр true, и тип, если он false. Результирующий тип доступен в статическом члене type.Несколько примеров:
using namespace std;
// здесь будет int
conditional<true, int, float>::type
// Можем использовать conditional_t для доступа к ::type напрямую
// здесь будет std::string
conditional_t<true, string, char*>
// Булев параметр может быть любым выражением, известным на момент компиляции
// здесь будет std::string
constexpr bool useStdString{true};
conditional_t<useStdString, string, char*>
// здесь будет std::vector<float>
conditional_t<false, vector<int>, vector<float>>
// здесь также будет std::vector<float>
vector<conditional_t<false, int, float>>
Обычно,
std::conditional используется для создания псевдонима/алиаса, который мы затем можем легко использовать в нашем коде:#include <iostream>
#include <type_traits> // for std::conditional
#include <typeinfo> // for typeid
constexpr bool Use64BitIntegers{true};
using integer =
std::conditional<Use64BitIntegers,
std::int64_t,
std::int32_t>::type;
int main() {
integer MyInt{42};
std::cout << typeid(integer).name() << " ("
<< sizeof(integer) << " bytes)";
}
// Output (варианты, зависит от компилятора):
// __int64 (8 bytes)
// l (8 bytes)
🔥2
Начиная с C++17, в лямбде можно захватить this по значению через *this (просто this захватит объект по ссылке):
Копия объекта может понадобиться например для исполнения лямбды в другом потоке, когда this уже может быть инвалидирован на момент вызова лямбды.
https://habr.com/ru/companies/otus/articles/794821/
class MyClass {
public:
int value = 10;
void printValue() {
auto lambda = [=, *this]() {
std::cout << "Value: " << value << std::endl;
};
lambda();
}
};
Копия объекта может понадобиться например для исполнения лямбды в другом потоке, когда this уже может быть инвалидирован на момент вызова лямбды.
https://habr.com/ru/companies/otus/articles/794821/
Хабр
Краткий обзор развития лямбда-выражений в C++11, C++14, C++17 и C++20
Привет, Хабр! Сегодня рассмотрим лямбда-выражения в C++ и их эволюцию с момента появления в стандарте C++11 и до последних обновлений в C++20. Лямбда - выражения в C++ — это анонимные функции, которые...
👍4
Забавная функция из <numeric> появилась в C++11 - называется std::iota, по всей видимости это греческая буква йота (⍳). Так функция называлась в языке программирования APL (вышел в 1966-м, на лого яблоко, впрочем не надкусанное).
Функция заполняет интервал последовательными значениями (++), начиная с переданного.
Функция заполняет интервал последовательными значениями (++), начиная с переданного.
#include <iostream>
#include <numeric> // iota
#include <vector>
int main()
{
std::vector<int> v;
v.assign(10, 0);
std::iota(v.begin(), v.end(), 10);
for (auto i : v) {
std::cout << i << " ";
}
return 0;
}
// Output:
// 10 11 12 13 14 15 16 17 18 19
В стандартной библиотеке есть несколько функций для работы с отсортированными контейнерами. Одна их них std::lower_bound:
Запомнить результат работы можно так: вернет итератор на позицию, перед которой можно вставить value, не нарушив порядок. Если такой позиции нет, то вернет last (обычно end() контейнера).
Так, в примере есть несколько подряд идущих 5, алгоритм вернет итератор на первую (если вставить 5 перед ним, то сортировка не нарушится).
В примере
При поиске элемента, большего всех, вернется конец контейнера, тк мы должны вставить его в конец, чтобы сохранить сортировку.
std::lower_bound(first, last, value) находит первый элемент в интервале [first, last) контейнера, который НЕ расположен до value, то есть не меньше value. Для сравнения используется operator<.Запомнить результат работы можно так: вернет итератор на позицию, перед которой можно вставить value, не нарушив порядок. Если такой позиции нет, то вернет last (обычно end() контейнера).
#include <iostream>
#include <algorithm>
#include <vector>
int main()
{
std::vector<int> v = {1, 2, 4, 5, 5, 5, 6, 7, 8};
auto print_lower_bound = [&](int x){
auto it = std::lower_bound(v.begin(), v.end(), x);
if (it != v.end()) {
std::cout << "[" << std::distance(v.begin(), it) << "] = "
<< *it << std::endl;
} else {
std::cout << "not found" << std::endl;
}
};
print_lower_bound(5); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
print_lower_bound(3); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
print_lower_bound(0); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
print_lower_bound(9); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
return 0;
}
// Output:
// [3] = 5
// [2] = 4
// [0] = 1
// not found
Так, в примере есть несколько подряд идущих 5, алгоритм вернет итератор на первую (если вставить 5 перед ним, то сортировка не нарушится).
В примере
2, 4 при поиске 3 будет возвращен итератор не на 2, а на 4, тк это первое значение для которого < 3 не сработает (если вставить 3 перед 4, то сортировка не нарушится).При поиске элемента, большего всех, вернется конец контейнера, тк мы должны вставить его в конец, чтобы сохранить сортировку.
Для того, чтоб найти первый элемент сортированной последовательности, который больше значения, можно применить std::upper_bound. Если сравнить результат работы с lower_bound, то на первый взгляд логика такая же - вернется позиция, перед которой можно вставить элемент, не нарушив сортировки:
Однако, как видим, если есть повторяющиеся элементы, то результат разный:
#include <iostream>
#include <algorithm>
#include <vector>
int main()
{
std::vector<int> v = {1, 2, 4, 5, 5, 5, 6, 7, 8};
auto print_upper_bound = [&](int x){
auto it = std::upper_bound(v.begin(), v.end(), x);
if (it != v.end()) {
std::cout << "[" << std::distance(v.begin(), it) << "] = "
<< *it << std::endl;
} else {
std::cout << "not found" << std::endl;
}
};
print_upper_bound(5); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
print_upper_bound(3); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
print_upper_bound(0); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
print_upper_bound(9); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
return 0;
}
// Output:
// [6] = 6
// [2] = 4
// [0] = 1
// not found
Однако, как видим, если есть повторяющиеся элементы, то результат разный:
print_lower_bound(5); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
print_upper_bound(5); // 1, 2, 4, 5, 5, 5, 6, 7, 8
// ^
👍4
std::string::substr возвращает новую строку, созданную как копию подстроки:basic_string substr( size_type pos = 0, size_type count = npos ) const;Первый параметр - это позиция (index) начала подстроки, а второй - количество символов (а НЕ позиция конца!). Второй параметр можно не передавать и тогда строка скопируется до конца.
Несколько примеров:
#include <iostream>
#include <string>
int main()
{
std::string str =
"In anger we should refrain both"
" from speech and action."; // Pythagoras
// string substr (size_t pos = 0, size_t len = npos) const;
// С позиции (индекса) == 3 взять 5 символов, получим "anger"
std::string strFrom3Len5 = str.substr(3, 5); // копия подстроки
std::cout << strFrom3Len5 << std::endl;
// Найдем позицию с которой "speech" начинается
std::size_t speechPos = str.find("speech");
if (speechPos != str.npos) { // нашли?
// Если не указать длину, то скопируется до конца
std::cout << str.substr(speechPos) // speech and action.
<< std::endl;
}
size_t strLen = str.size();
// Если pos равен длине строки, то вернется пустая строка
std::cout << str.substr(strLen).size() << std::endl; // 0
// Получить подстроку у пустой строки с 0 тоже можно
std::cout << std::string("").substr(0).size() << std::endl; // 0
try
{
// Если же pos больше длины строки,
// то будет исключение std::out_of_range
std::cout << str.substr(strLen + 1).size() << std::endl;
}
catch (const std::out_of_range& ex)
{
std::cout << ex.what() // invalid string position
<< std::endl;
}
// Если длина len больше, чем число оставшихся символов,
// то подстрока идет до конца, без исключения
std::cout << str.substr(speechPos, 1000) // speech and action.
<< std::endl;
}
// Output:
// anger
// speechand action.
// 0
// 0
// invalid string position
// speech and action.
Type traits часть 6 (перевод) - финал!
Мы не ограничены трейтами, которые идут вместе со стандартной библиотекой. Можно взять их из сторонних библиотек или написать свои.
Представим, что у нас есть шаблонная функция, которая получает объекты, которые либо имеют, либо не имеют функцию
Трейты из std обычно предоставляют булево значение в виде поля
Обычно мы хотим
Далее, если для какого-то класса мы хотим значение трейта равное
Теперь с этими четырьмя линиями кода запустим наш трейт:
Вывод:
Мы не ограничены трейтами, которые идут вместе со стандартной библиотекой. Можно взять их из сторонних библиотек или написать свои.
Представим, что у нас есть шаблонная функция, которая получает объекты, которые либо имеют, либо не имеют функцию
render. Напишем трейт is_renderable:template <typename T>
void Function(T Param) {
if constexpr (is_renderable<T>::value) {
Param.Render();
} else {
std::cout << "\nNot Renderable";
}
}
Трейты из std обычно предоставляют булево значение в виде поля
value. Мы можем легко добиться такого же поведения от наших собственных структур, наследуясь от хелперов std::false_type или std::true_type, доступных в заголовке <type_traits>.Обычно мы хотим
value иметь по-умолчанию false, поэтому мы наследуемся от false_type:template <typename>
struct is_renderable : std::false_type {};
Далее, если для какого-то класса мы хотим значение трейта равное
true, мы предоставляем специализацию, которая наследуется от true_type:template <>
struct is_renderable<MyClass> : std::true_type {};
Теперь с этими четырьмя линиями кода запустим наш трейт:
#include <type_traits>
#include <iostream>
template <typename>
struct is_renderable : std::false_type {};
class Fish {
public:
void Render() {
std::cout << "Fish: ><((((`>";
}
};
template <>
struct is_renderable<Fish> : std::true_type {};
class Sound {};
template <typename T>
void Function(T Param) {
if constexpr (is_renderable<T>::value) {
Param.Render();
} else {
std::cout << "\nNot Renderable";
}
}
int main() {
Fish MyFish;
Function(MyFish);
Sound MySound;
Function(MySound);
}
Вывод:
Fish: ><((((`>
Not Renderable
👍1
std::distance(_InIt first, _InIt last) возвращает "расстояние" - число переходов от первого итератора ко второму.Для итераторов со случайным доступом (random access), например итератором для
vector, эта функция будет работать за O(1) и вернет их разность. Казалось бы, мы можем просто сами найти эту разность итераторов и дело с концом, зачем вообще нужна эта функция?Однако, для итераторов без случайного доступа (например
list), расстояние тоже найдется, но уже путем линейного прохода от одного итератора к другому. Просто разность итераторов тут найти не получится, так как для таких итераторов она просто не определена. Время работы функции в таком случае будет O(n). Таким образом std::distance позволяет найти расстояние по любому типу итераторов, как и многое в std.Пример:
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>
int main()
{
std::vector<int> v = {10, 20, 30, 40, 50};
std::cout << std::distance(v.begin(), v.end())
<< " " << v.end() - v.begin()
<< std::endl; // 5 5
std::cout << std::distance(
std::next(v.begin()), // ++
std::prev(v.end()) // --
) << std::endl; // 3
auto it = v.begin();
std::advance(it, 3); // продвинемся на 3 вперед
std::cout << std::distance(v.end(), it) << std::endl; // -2
std::list<int> list = { 5, 6, 7, 8 };
std::cout << std::distance(list.cbegin(), list.cend()) << std::endl; // 4
//std::cout << list.end() - list.begin(); // нельзя
std::cout << std::distance(list.cend(), list.cbegin()) << std::endl; // 1 - неверно, тк не random access, UB
std::map<int, int> m = { {1,2}, {3,4}, {5, 6} };
std::cout << std::distance(m.find(3), m.end()) << std::endl; // 2
return 0;
}
// Output:
// 5 5
// 3
// -2
// 4
// 1
// 2
Интересно, что std::distance может вернуть отрицательное значение для random access, если первый итератор дальше второго, что является вполне валидным:
std::distance(v.end(), v.begin()) // -v.size()
Для не random access у меня вернулась ерунда, хотя в таком случае вообще будет UB.
Класс, перегружающий оператор вызова (
Напишем в качестве примера класс, который выводит строки в поток через разделитель (пробел по умолчанию). Обращаю внимание, что при перегрузке оператора вызова первые скобки пишутся в названии оператора, а вторые принимают аргументы функции:
После создания объекта
Объекты-функции часто используют как аргументы обобщенных алгоритмов, хотя зачастую проще использовать лямбды.
operator() - круглые скобки), позволяет использовать объекты этого типа как функции. Во всем остальном такие классы являются обычными - могут содержать другие методы, хранить состояние и тп.Напишем в качестве примера класс, который выводит строки в поток через разделитель (пробел по умолчанию). Обращаю внимание, что при перегрузке оператора вызова первые скобки пишутся в названии оператора, а вторые принимают аргументы функции:
#include <iostream>
#include <string>
#include <iomanip>
#include <vector>
#include <algorithm>
class StringPrinter
{
public:
StringPrinter(
const std::string& delim = " ",
std::ostream& stream = std::cout)
: _d(delim), _s(stream)
{
}
~StringPrinter()
{
_s << std::endl;
}
void operator()(const std::string& str)
{
if (_first) {
_first = false;
} else {
_s << _d;
}
_s << std::quoted(str);
}
std::string delim() const
{
return _d;
}
private:
std::string _d;
std::ostream& _s;
bool _first = true;
};
int main()
{
{
StringPrinter print;
print("a");
print("b");
print.operator()("c");
} // "a" "b" "c"
std::vector<std::string> vs = {
"How", "long", "are", "you", "going",
"to", "wait", "before", "you", "demand",
"the", "best", "for", "yourself?"};
std::for_each(vs.cbegin(), vs.cend(), StringPrinter("_"));
return 0;
}
После создания объекта
StringPrinter print мы можем "вызвать" его с помощью круглых скобок, а также напрямую print.operator()("c").Объекты-функции часто используют как аргументы обобщенных алгоритмов, хотя зачастую проще использовать лямбды.
👍1
В заголовке <functional> описаны различные встроенные функциональные объекты.
* Арифметические:
- plus (x + y)
- minus (x - y)
- multiplies (x * y)
- divides (x / y)
- modulus (x % y)
- negate (-x)
* Сравнения (наверное, самые используемые):
- equal_to (x == y)
- not_equal_to (x != y)
- greater (x > y)
- less (x < y)
- greater_equal (x >= y)
- less_equal (x <= y)
* Логические
- logical_and (x && y)
- logical_or (x || y)
- logical_not (!x)
* Битовые и другие
Рассмотрим распространенный пример - сортировку. По-умолчанию, std::sort будет выполнять сортировку по возрастанию, что обеспечит вызов operator<.
Если же мы хотим отсортировать по убыванию (макс. вперед), то можно воспользоваться функциональным объектом std:;greater. На самом деле это шаблонная структура с перегруженным оператором вызова (реализует >), но начиная с C++ 14 тип можно не указывать:
Пример:
* Арифметические:
- plus (x + y)
- minus (x - y)
- multiplies (x * y)
- divides (x / y)
- modulus (x % y)
- negate (-x)
* Сравнения (наверное, самые используемые):
- equal_to (x == y)
- not_equal_to (x != y)
- greater (x > y)
- less (x < y)
- greater_equal (x >= y)
- less_equal (x <= y)
* Логические
- logical_and (x && y)
- logical_or (x || y)
- logical_not (!x)
* Битовые и другие
Рассмотрим распространенный пример - сортировку. По-умолчанию, std::sort будет выполнять сортировку по возрастанию, что обеспечит вызов operator<.
Если же мы хотим отсортировать по убыванию (макс. вперед), то можно воспользоваться функциональным объектом std:;greater. На самом деле это шаблонная структура с перегруженным оператором вызова (реализует >), но начиная с C++ 14 тип можно не указывать:
template< class T = void >
struct greater;
Пример:
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
int main()
{
std::vector<int> v1 = { 5, 1, 2, 4, 3 };
auto v2 = v1; // сделаем копию
std::sort(v1.begin(), v1.end()); // 1 2 3 4 5
for (auto i : v1)
std::cout << i << " ";
std::cout << std::endl;
std::sort(
v2.begin(),
v2.end(),
std::greater()); // 5 4 3 2 1
for (auto i : v2)
std::cout << i << " ";
std::cout << std::endl;
return 0;
}
👍4
Интересная особенность библиотечных объектов функций заключается в том, что реализация гарантирует их работоспособность с указателями. Вообще, результат сравнения (<, >) двух несвязанных указателей не определен. Но мы можем захотеть отсортировать н-р контейнер указателей по их адресу. Утверждается, что например сравнивать их на < нельзя, а вот std::less гарантирует верную реализацию (не знаю как):
На самом деле у меня получились одинаковые результаты, но это не значит, что первый вариант можно использовать.
Более того, ассоциативные контейнеры используют для упорядочивания своих элементов std::less, поэтому можно определить set из указателей или map с указателем в качестве ключа. А еще std::sort начиная с C++20 использует std::less, а не operator< для сортировки.
Вывод: иcпользуйте std::less и std::greater - так короче и безопасней.
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <string>
int main()
{
std::string s1, s2;
std::vector<std::string*> vp;
vp.push_back(&s1);
vp.push_back(&s2);
vp.push_back(new std::string("memory leak"));
auto vp2 = vp; // copy
std::sort(vp.begin(), vp.end(), [](std::string* l, std::string* r) {
return l < r; // непредсказуемый результат
});
auto print_vector_pointers = [&](std::vector<std::string*> v){
for (auto p : vp) {
std::cout << (size_t)p << " ";
}
std::cout << std::endl;
};
print_vector_pointers(vp);
std::sort(vp2.begin(), vp2.end(),
std::less<std::string*>()); // ok
print_vector_pointers(vp2);
return 0;
}
На самом деле у меня получились одинаковые результаты, но это не значит, что первый вариант можно использовать.
Более того, ассоциативные контейнеры используют для упорядочивания своих элементов std::less, поэтому можно определить set из указателей или map с указателем в качестве ключа. А еще std::sort начиная с C++20 использует std::less, а не operator< для сортировки.
Вывод: иcпользуйте std::less и std::greater - так короче и безопасней.
👍1
Как поймать исключение в другом потоке и передать в текущий?
Использовать std::exception_ptr.
Как видим получить указатель на текущее исключение (даже если мы ловим все с помощью ...), можно вызовом std::current_exception.
Возбудить исключение по указателю можно так:
Использовать std::exception_ptr.
Как видим получить указатель на текущее исключение (даже если мы ловим все с помощью ...), можно вызовом std::current_exception.
Возбудить исключение по указателю можно так:
if (eptr)
std::rethrow_exception(eptr);
🔥6
std::size - это в некотором роде универсальная функция, которая вернет размер переданного интервала.
Ей можно передать любой контейнер, std::string или std:;string_view - для них просто вызовется size().
Также можно передать как C-style массив, так и std::array.
Вобщем, всеядная вещь. Доступна с C++17
Ей можно передать любой контейнер, std::string или std:;string_view - для них просто вызовется size().
Также можно передать как C-style массив, так и std::array.
Вобщем, всеядная вещь. Доступна с C++17
#include <iostream>
#include <vector>
#include <list>
#include <array>
int main()
{
std::vector<int> v = { 1, 2, 3, 4, 5 };
std::cout << std::size(v) << std::endl; // 5
std::list<std::string> list = {"a", "b", "c"};
std::cout << std::size(list) << std::endl; // 3
std::string s = "abc";
std::cout << std::size(s) << std::endl; // 3
std::string_view sv{"Look, ma, I'm modern guy!"};
std::cout << std::size(sv) << std::endl; // 25
std::array<int, 4> sa = {1, 2};
std::cout << std::size(sa) << std::endl; // 4
int a[]{ 1, 2, 3, 4, 5 };
// Старый сишный способ
std::cout << (sizeof(a) / sizeof(a[0])) << std::endl; // 5
// Новый нормальный
std::cout << std::size(a) << std::endl; // 5
// Для массива символов вернет c \0
const char carr[] = "12345";
std::cout << std::size(carr) << std::endl; // 6 не 5!
return 0;
}
👍1