Определите, при каких условиях свёртка параметр-пака без начального значения остаётся корректной, если пакет пуст.
Унарная свёртка пустого параметр-пака корректна только для операторов &&, || и ,. Для них стандарт задаёт нейтральные значения: true, false и void() соответственно. Унарная свёртка с любым другим оператором при пустом пакете некорректна и приводит к ошибке компиляции.
До C++17 операции над всеми элементами параметр-пака обычно реализовывали рекурсией или вспомогательными метафункциями. Это увеличивало объём шаблонного кода и усложняло обработку пустого пакета.
Свёртки появились в C++17 как специальный механизм для компактного применения бинарного оператора ко всем аргументам пакета. При этом язык отдельно определяет поведение некоторых операторов для пустого пакета, чтобы типичные логические операции имели математически согласованный результат.
Шаблон может получить ноль аргументов даже тогда, когда обычно вызывается с непустым пакетом. Если операция не имеет естественного нейтрального значения, компилятор не может построить корректное выражение.
Неверное предположение о поддержке пустого пакета приводит не к значению по умолчанию, а к ошибке инстанцирования шаблона. Это особенно важно для универсальных библиотечных функций, где набор аргументов определяется вызывающим кодом.
Унарная свёртка имеет вид применения одного оператора ко всему пакету без отдельного начального значения. Для непустого пакета она корректна для операторов, разрешённых синтаксисом свёртки, но для пустого пакета стандарт делает три исключения:
&& даёт true;|| даёт false;, даёт void().Такое поведение соответствует нейтральным элементам операций. Проверка «все условия истинны» для пустого набора считается истинной, проверка «хотя бы одно условие истинно» — ложной, а пустая последовательность через запятую не производит значения.
Вызов all<> корректен: пустая свёртка по && возвращает true. Если заменить && на +, аналогичная функция не сможет обработать пустой пакет, поскольку для такой унарной свёртки нейтральное значение стандартом не задано.
Это не означает, что любой оператор нельзя использовать с пустым набором вообще. Бинарная свёртка содержит явное начальное значение, поэтому результат при пустом пакете может быть получен из этого значения. Однако выбранное начальное значение должно быть частью контракта шаблона и действительно соответствовать смыслу операции.
В библиотеке выполняется проверка набора свойств типа. Пустой набор означает отсутствие ограничений, поэтому естественный результат проверки — true.
Можно было запретить пустой пакет отдельным ограничением. Это защищает от случайного вызова, но делает интерфейс менее универсальным и требует дополнительной диагностики. Другой вариант — использовать бинарную свёртку с явно переданным true; он нагляднее фиксирует начальное значение, но добавляет его в реализацию и может быть менее лаконичным.
Выбранная унарная свёртка по && оправдана, если семантика пустого набора действительно совпадает с логической истиной. В результате шаблон поддерживает ноль и любое количество аргументов без рекурсии и специальных ветвей. Если же пустой набор должен считаться ошибкой, его следует явно запретить ограничением, а не полагаться на поведение свёртки.
Одинаковы ли правила для унарной и бинарной свёртки при пустом пакете?
Нет. Унарная свёртка без начального значения имеет встроенные исключения только для &&, || и ,. Бинарная свёртка использует явно заданный начальный операнд, поэтому при пустом пакете результатом становится именно этот операнд. Это позволяет сворачивать, например, числовой пакет относительно заданного начального значения, но ответственность за корректность этого значения лежит на разработчике.
Можно ли считать свёртку по && безопасной с точки зрения вычисления аргументов?
Короткое замыкание действует внутри итогового выражения свёртки: последующие операнды && могут не вычисляться, если предыдущий дал false. Однако сами аргументы функции уже вычисляются до входа в функцию. Поэтому свёртка может пропустить вызов переданных ленивых операций, но не способна отменить побочные эффекты, произошедшие при формировании аргументов вызова.
Чем опасна замена логической свёртки на свёртку по оператору, который не является ассоциативным?
Результат может зависеть от выбранного направления свёртки. Для операторов вроде вычитания или деления левосторонняя и правосторонняя группировка обычно дают разные значения, поэтому нельзя бездумно считать их взаимозаменяемыми. Кроме того, у таких операторов нет общего стандартного результата для пустого унарного пакета, и вызов с нулём аргументов будет некорректен.