Выбрать главу

std::cout << sum(a, b) << '\n'; // Вывод: Hello World

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

Только что мы написали код, в котором с помощью простой рекурсии бинарный оператор (+) применяется к заданным параметрам. Как правило, это называется сверткой. В C++17 появились выражения свертки, которые помогают выразить ту же идею и при этом писать меньше кода.

Подобное выражение называется унарной сверткой. C++17 позволяет применять к пакетам параметров свертки следующие бинарные операторы: +, , *, /, %, ^, &, |,

=, <, >, <<, >>, +=, –=, *=, /=, %=, ^=, &=, |=, <<=, >>=, ==, !=, <=, >=, &&, ||, ,, .*, –>*.

Кстати, в нашем примере кода неважно, какую использовать конструкцию, (ts + …) или (… + ts);. Они обе работают так, как нужно. Однако между ними есть разница, которая может иметь значение в других случаях: если многоточие находится с правой стороны оператора, то такое выражение называется правой сверткой. Если же оно находится с левой стороны, то это левая свертка.

В нашем примере с суммой левая унарная свертка разворачивается в конструкцию 1+(2+(3+(4+5))), а правая унарная свертка развернется в (((1+2)+3)+4)+5. В зависимости от того, какой оператор используется, могут проявиться нюансы. При добавлении новых чисел ничего не меняется.

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

Если кто-то вызовет функцию sum() и не передаст в нее аргументы, то пакет параметров произвольной длины не будет содержать значений, которые могут быть свернуты. Для большинства операторов такая ситуация считается ошибкой (но для некоторых — нет, вы увидите это чуть позже). Далее нужно решить, генерировать ошибку или же вернуть конкретное значение. Очевидным решением будет вернуть значение 0.

Это делается так:

template <typename ... Ts>

auto sum(Ts ... ts)

{

  return (ts + ... + 0);

}

Таким образом, вызов sum() возвращает значение 0, а вызов sum(1, 2, 3) — значение (1+(2+(3+0))). Подобные свертки с начальным значением называются бинарными.

Кроме того, обе конструкции, (ts + ... + 0) и (0 + ... + ts), работают как полагается, но такая бинарная свертка становится правой или левой соответственно. Взгляните на рис. 1.2.

При использовании бинарных сверток для решения такой задачи, когда аргументы отсутствуют, очень важны нейтральные элементы — в нашем случае сложение любого числа с нулем ничего не меняет, что делает 0 нейтральным элементом. Поэтому можно добавить 0 к любому выражению свертки с помощью операторов + или . Если пакет параметров пуст, это приведет к возврату функцией значения 0. С математической точки зрения это правильно. С точки зрения реализации нужно определить, что именно является правильным в зависимости от наших требований.

Тот же принцип применяется и к умножению. Здесь нейтральным элементом станет 1:

template <typename Ts>

auto product(Ts ts)

{

  return (ts * ... * 1);

}

Результат вызова product(2, 3) равен 6, а результат вызова product() без параметров равен 1.

В логических операторах И (&&) и ИЛИ (||) появились встроенные нейтральные элементы. Свертка пустого пакета параметров с оператором && заменяется на true, а свертка пустого пакета с оператором || — на false.

Еще один оператор, для которого определено значение по умолчанию, когда он используется для пустых пакетов параметров, — это оператор «запятая» (,), заменяемый на void().

Давайте взглянем на другие вспомогательные функции, которые можно реализовать с помощью этих механизмов.

Соотнесение диапазонов и отдельных элементов 

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

template <typename R, typename ... Ts>

auto matches(const R& range, Ts ... ts)

{

  return (std::count(std::begin(range), std::end(range), ts) + ...);

}

Вспомогательная функция использует функцию std::count из библиотеки STL. Она принимает три параметра: первые два представляют собой начальный и конечный итераторы того или иного итерабельного промежутка, а третий параметр — это значение, с которым будут сравниваться все элементы промежутка. Метод std::count возвращает количество всех элементов внутри диапазона, равных третьему параметру.