📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 ... 3 4 5 6 7 8 9 10 11 ... 121
Перейти на страницу:
выражений свертки (пример, связанный с выражениями свертки, мы рассмотрим далее в этой главе). Полученная сумма сохраняется в переменную-член value. Теперь вопрос заключается в том, что за тип — T? Если мы не хотим указывать его явно, то ему следует зависеть от типов значений, переданных в конструктор. В случае передачи объектов-строк тип должен быть std::string. При передаче целых чисел тип должен быть int. Если мы передадим целые числа, числа с плавающей точкой и числа с удвоенной точностью, то компилятору следует определить, какой тип подходит всем значениям без потери точности. Для этого мы предоставляем явные правила выведения типов:

template <typename ... Ts>

sum(Ts&& ... ts) -> sum<std::common_type_t<Ts...>>;

Согласно этим правилам компилятор может использовать типаж std::common_ type_t, который способен определить, какой тип данных подходит всем значениям. Посмотрим, как его применить:

sum s {1u, 2.0, 3, 4.0f};

sum string_sum {std::string{"abc"}, "def"};

std::cout << s.value << 'n'

          << string_sum.value << 'n';

В первой строке мы создаем объект типа sum на основе аргументов конструктора, имеющих типы unsigned, double, int и float. Типаж std::common_type_t возвращает тип double, поэтому мы получаем объект типа sum<double>. Во второй строке мы предоставляем экземпляр типа std::string и строку в стиле C. В соответствии с нашими правилами компилятор создает экземпляр типа sum<std::string>.

При запуске этот код выведет значение 10 как результат сложения чисел и abcdef в качестве результата объединения строк.

Упрощаем принятие решений во время компиляции с помощью constexpr-if

 В коде, содержащем шаблоны, зачастую необходимо по-разному выполнять определенные действия в зависимости от типа, для которого конкретный шаблон был специализирован. В С++17 появились выражения constexpr-if, позволяющие значительно упростить написание кода в таких ситуациях.

Как это делается

В этом примере мы реализуем небольшой вспомогательный шаблонный класс. Он может работать с разными типами, поскольку способен выбирать различные пути выполнения кода в зависимости от типа, для которого мы конкретизируем шаблон.

1. Напишем обобщенную часть кода. В нашем примере рассматривается простой класс, который добавляет значение типа U к элементу типа T с помощью функции add:

template <typename T>

class addable

{

  T val;

public:

  addable(T v) : val{v} {}

  template <typename U>

  T add(U x) const {

    return val + x;

  }

};

2. Представим, что тип T — это std::vector<что-то>, а тип U — просто int. Каков смысл выражения «добавить целое число к вектору»? Допустим, нужно добавить данное число к каждому элементу вектора. Это делается в цикле:

template <typename U>

T add(U x)

{

  auto copy (val); // Получаем копию элемента вектора

  for (auto &n : copy) {

    n += x;

  }

  return copy;

}

3. Следующий и последний шаг заключается в том, чтобы объединить оба варианта. Если T — это вектор, состоящий из элементов типа U, то выполняем цикл. В противном случае выполняем обычное сложение.

template <typename U>

T add(U x) const {

  if constexpr (std::is_same_v<T, std::vector<U>>) {

    auto copy (val);

    for (auto &n : copy) {

      n += x;

    }

    return copy;

  } else {

    return val + x;

  }

}

4. Теперь класс можно использовать. Посмотрим, насколько хорошо он может работать с разными типами, такими как int, float, std::vector<int> и std::vector<string>:

addable<int>{1}.add(2);               // результат - 3

addable<float>{1.0}.add(2);           // результат - 3.0

addable<std::string>{"aa"}.add("bb"); // результат - "aabb"

std::vector<int> v {1, 2, 3};

addable<std::vector<int>>{v}.add(10);

  // is std::vector<int>{11, 12, 13}

std::vector<std::string> sv {"a", "b", "c"};

addable<std::vector<std::string>>{sv}.add(std::string{"z"});

  // is {"az", "bz", "cz"}

Как это работает

Новая конструкция constexpr-if работает точно так же, как и обычные конструкции if-else. Разница между ними заключается в том, что значение условного выражения определяется во время компиляции. Весь код завершения, который компилятор сгенерирует из нашей программы, не будет содержать дополнительных ветвлений, относящихся к условиям constexpr-if. Кто-то может сказать, что эти механизмы работают так же, как и макросы препроцессора #if и #else, предназначенные для подстановки текста, но в данном случае всему коду даже не нужно быть синтаксически правильным. Ветвления конструкции constexpr-if должны быть синтаксически правильными, но неиспользованные ветви не обязаны быть семантически корректными.

Чтобы определить, должен ли код добавлять значение х к вектору, задействуем типаж std::is_same. Выражение std::is_same<A, B>::value вычисляется в логическое значение true, если A и B имеют один и тот же тип. В нашем примере применяется условие std::is_same<T, std::vector<U>>::value, которое имеет значение true, если пользователь конкретизировал шаблон для класса T = std::vector<X> и пробует вызвать функцию add с параметром типа U = X.

В одном блоке constexpr-if-else может оказаться несколько условий (обратите внимание, что a и b должны зависеть от параметров шаблона, а не только от констант времени компиляции):

if constexpr (a) {

  // что-нибудь делаем

} else if constexpr (b) {

  // делаем что-нибудь еще

} else {

  // делаем нечто совсем другое

}

С помощью C++17 гораздо легче как выразить, так и прочитать код, получающийся при метапрограммировании.

Дополнительная информация 

Для того чтобы убедиться, каким прекрасным новшеством являются конструкции constexpr-if для C++, взглянем, как решалась та же самая задача до С++17:

template <typename T>

class addable

{

  T val;

public:

  addable(T v) : val{v} {} template <typename U>

  std::enable_if_t<!std::is_same<T, std::vector<U>>::value, T>

  add(U x) const { return val + x; }

  template <typename U>

  std::enable_if_t<std::is_same<T, std::vector<U>>::value,

                   std::vector<U>>

  add(U x) const {

    auto copy (val);

    for (auto &n : copy) {

     

1 ... 3 4 5 6 7 8 9 10 11 ... 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?