Skip to content

Latest commit

 

History

History
466 lines (335 loc) · 13.2 KB

17-Misc-1.md

File metadata and controls

466 lines (335 loc) · 13.2 KB

Всякая всячина (1)

Содержание

if constexp

Данное средство позволяет вычислять оператор if в процессе построения программы, что дает возможность организовать условную компиляцию (аналог препроцессора).

template <typename T> 
auto get_value(T t)
{
   if constexpr (std::is_pointer_v<T>) 
     return *t;
   else
     return t;
}

Трейты типов

Трейты типов (характеристики типов) - это шаблонный механизм, позволяющий анализировать типы.

#include <iostream>
#include <type_traits>

class Class {};

int main() 
{
    std::cout << std::is_floating_point<Class>::value << '\n';  // 0
    std::cout << std::is_floating_point<float>::value << '\n';  // 1
    std::cout << std::is_floating_point<int>::value << '\n';    // 0
}

Проверки происходят во время построения программы.

Как устроены трейты?

В результате компиляции будут сгенерированы следующие структуры:

struct is_floating_point_Class {
    static const bool value = false;
};

struct is_floating_point_float {
    static const bool value = true;
};

struct is_floating_point_int {
    static const bool value = false;
};

Обращения к ним во время выполнения приведут к печати нужных значений.

С помощью трейтов можно проверять параметры шаблонов, проводить условную компиляцию (без использования препроцессора)

if constexpr(std::is_signed<T>::value)
        algorithm_signed(t);
else if constexpr (std::is_unsigned<T>::value)
        algorithm_unsigned(t);

Табличка полезных трейтов:

Более удобная запись трейтов типов появилась в стандарте C++14:

std::is_pointer_v<T> // вместо std::is_pointer<T>::value

Pairs and tuples

Пары и кортежи появились в С++ в разное время, но завоевали определенную известность.

Например, при работе с контейнером map используем пары:

std::pair<std::string,std::string> key;
edMap.insert(make_pair(key,d));

// или так

std::pair<std::string,std::string> key;
edMap[key] = d;

При просмотре содержимого map пары очень полезны:

for (auto it=mymap.begin(); it!=mymap.end(); ++it)
    std::cout << it->first << " => " << it->second << '\n';

Еще один пример использования pair, когда из функции необходимо вернуть 2 значения:

template <typename T>
std::pair<bool, T> parse_string(const std::wstring &s)
{
    std::wistringstream iss(s);
    T t;
    bool success = !(iss >> t).fail();
    return std::make_pair(success, t);
}

auto r1 = parse_string<int>(L"123");
if (r1.first)
    cout << r.second;
else
    cout << "fail";

Теперь о кортежах.

Их также можно использовать для возвращения из функции набора значений:

std::tuple<int, int, double> foo(int a, int b) {
  return std::make_tuple(a+b, a*b, double(a)/double(b));
}

Традиционный способ получения значения кортежа:

t.get<n>();
// или
get<n>(t);

по номеру элемента n.

В стандарте С++17 появилось несколько упрощений при работе с кортежами.

Во-первых, стало проще их формировать:

std::tuple<int, int, int, int> foo(int a, int b)    {
    return {a + b, a - b, a * b, a / b};
}

Во-вторых, появились структурные связывания, которые упрощают получение элементов:

auto [add, sub, mul, div] = foo(5,12);

При работе с lvalue ссылками нужно использовать std::tie:

std::tuple<int&, int&> minmax( int& a, int& b ) {
  if (b<a)
    return std::tie(b,a);
  else
    return std::tie(a,b);
}

Структурные связывания

Связывания (bindings) уже упоминались выше. Теперь можно привести полезные примеры их использования

std::map<std::string, int> m;
...
for (auto const& [key, value] : m) {
    std::cout << "The value for " << key << " is " << value << '\n';
}

Есть ограничения:

нельзя явно указывать типы элементов нельзя использовать вложенные связывания

Ключевое слово mutable

Когда мы пытаемся писать код, корректный с точки зрения использования понятия константности, то столкнёмся с тем, что семантическая неизменность не эквивалентна синтаксической неизменности. Другими словами, нам может понадобиться изменить состояние объекта (если этого требуют детали реализации), сохранив при этом видимое извне состояние объекта константным.

Изменение внутреннего состояния может требоваться по каким-то глубоко техническим причинам и это не должно быть заметно для внешних клиентов нашего класса. Но выбор у нас не большой — если мы используем ключевое слово const при объявлении метода, то компилятор не позволит нам изменить объект этого класса, даже если эти изменения никто вне класса и не заметит.

см. пример (https://habr.com/ru/company/infopulse/blog/341264/)

class Polygon {
  std::vector<Vertex> vertices;
  mutable double cachedArea{0};
  mutable std::mutex mutex;
public:
  //...

  double area() const {
    auto area = cachedArea;
    if (area == 0) {
      std::scoped_lock lock{mutex};
      area = geometry::calculateArea(vertices);
      cachedArea = area;
    }
    return area;
  }

Также ключевое слово mutable может быть применено ко всей лямбда-функции, что сделает все её переменные изменяемыми:

int main() {
  int i = 2;
  auto ok = [i, x{22}]() mutable { i++; x+=i; };
}

Разработка класса range

В качестве полезного класса можно рассмотреть range - диапазон.

#include <iostream>

class num_iterator {
    int i;
public:

    explicit num_iterator(int position = 0) : i{position} {}

    int operator*() const { return i; }

    num_iterator& operator++() {
        ++i;
        return *this;
    }

    bool operator!=(const num_iterator &other) const {
        return i != other.i;
    }
};

class num_range {
    int a;
    int b;

public:
    num_range(int from, int to)
        : a{from}, b{to}
    {}

    num_iterator begin() const { return num_iterator{a}; }
    num_iterator end()   const { return num_iterator{b}; }
};

int main()
{
    num_range r {100, 110};

    for (int i : r) {
        std::cout << i << ", ";
    }
    std::cout << '\n';
    return 0;
}

Автоматический вывод параметров шаблонов

Вместо

std::lock_guard<std::mutex> guard(mutex);

можно будет писать

std::lock_guard guard(mutex);

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

Обзоры строк

Совершенно безопасно использовать std::string_view как параметр функции, если функции нужно не владение, а обзор строки, и не требуется сохранять обзор для последующего использования в другом месте.

Класс string_view хорош тем, что он легко конструируется и из std::string и из const char* без дополнительного выделения памяти. А ещё он имеет поддержку constexpr и повторяет интерфейс std::string. Но есть минус: для string_view не гарантируется наличие нулевого символа на конце.

#include <iostream>
#include <string_view>
 
int main()
{
  std::string_view text{ "hello" }; // представление для строки "hello", которое хранится в бинарном виде
  std::string_view str{ text }; // представление этой же строки - "hello"
  std::string_view more{ str }; // представление этой же строки - "hello"
 
  std::cout << text << ' ' << str << ' ' << more << '\n';
 
  return 0;
}

Пример 1

#include <iostream>
#include <functional>
#include <list>
#include <map>

using namespace std;

struct billionaire {
    string name;
    double dollars;
    string country;
};

int main()
{
    list<billionaire> billionaires {
        {"Bill Gates", 86.0, "USA"},
        {"Warren Buffet", 75.6, "USA"},
        {"Jeff Bezos", 72.8, "USA"},
        {"Amancio Ortega", 71.3, "Spain"},
        {"Mark Zuckerberg", 56.0, "USA"},
        {"Carlos Slim", 54.5, "Mexico"},
        // ...
        {"Bernard Arnault", 41.5, "France"},
        // ...
        {"Liliane Bettencourt", 39.5, "France"},
        // ...
        {"Wang Jianlin", 31.3, "China"},
        {"Li Ka-shing", 31.2, "Hong Kong"}
        // ...
    };

    map<string, pair<const billionaire, size_t>> m;

    for (const auto &b : billionaires) {
        auto [iterator, success] = m.try_emplace(b.country, b, 1);

        if (!success) {
            iterator->second.second += 1;
        }
    }


    for (const auto & [key, value] : m) {
        const auto &[b, count] = value;

        cout << b.country << " : " << count << " billionaires. Richest is "
             << b.name << " with " << b.dollars << " B$\n";
    }
}

Пример 2

#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
#include <random>

using namespace std;

static void print(const vector<int> &v)
{
    copy(begin(v), end(v), ostream_iterator<int>{cout, ", "});
    cout << '\n';
}

int main()
{
    vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    random_device rd;
    mt19937 g {rd()};

    cout << is_sorted(begin(v), end(v)) << '\n';

    shuffle(begin(v), end(v), g);

    cout << is_sorted(begin(v), end(v)) << '\n';
    print(v);

    sort(begin(v), end(v));

    cout << is_sorted(begin(v), end(v)) << '\n';
    print(v);

    shuffle(begin(v), end(v), g);

    partition(begin(v), end(v), [](int i) { return i < 5; });

    print(v);

    shuffle(begin(v), end(v), g);

    auto middle (next(begin(v), int(v.size()) / 2));
    partial_sort(begin(v), middle, end(v));

    print(v);

    struct mystruct {
        int a;
        int b;
    };

    vector<mystruct> mv {{5, 100}, {1, 50}, {-123, 1000}, {3, 70}, {-10, 20}};

    sort(begin(mv), end(mv),
        [] (const mystruct &lhs, const mystruct &rhs) {
            return lhs.b < rhs.b;
        });

    for (const auto &[a, b] : mv) {
        cout << "{" << a << ", " << b << "} ";
    }
    cout << '\n';
}

Пример 3

#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <algorithm>

using namespace std;

int main()
{
    vector<string> v {istream_iterator<string>{cin}, {}};
    sort(begin(v), end(v));

    do {
        copy(begin(v), end(v), ostream_iterator<string>{cout, ", "});
        cout << '\n';
    } while (next_permutation(begin(v), end(v)));
}