Принтуем с fold expression



Для начала разберем, как бы все выглядело до С++17.



Задача - вывести на экран все аргументы функции подряд. Не так уж и сложно. Код будет выглядеть примерно вот так:



void print() {

}



template<typename T1, typename... T>

void print(T1 s, T... ts) {

std::cout << s;

print(ts...);

}



print(1.7, -2, "qwe");



// Output: 1.7-2qwe




Это будет работать, но не очень прикольно выводить аргументы прям подряд символами. Нужно какое-то форматирование. Например, между аргументами выводить пробел, а в конце перенести строку. Тут уже все несколько усложняется...



void print_impl() {

}



template<typename T1, typename... T>

void print_impl(T1 s, T... ts) {

std::cout << ' ' << s;

print_impl(ts...);

}



template<typename T1, typename... T>

void print(T1 s, T... ts) {

std::cout << s;

print_impl(ts...);

std::cout << std::endl;

}



print(1.7, -2, "qwe");

print("You", ", our subscribers,", "are", "the", "best!!");



// Output:

// 1.7 -2 qwe

// You , our subscribers, are the best!!




Нам мало того, что пришлось использовать базу рекурсии, так еще и прокси-функцию, которая допиливает форматирование. Слишко МНОГА БУКАВ. Ща исправим.



Вот так будет выглядеть базовый принт без форматирования на fold expression:



template<typename... Args>

void print(Args&&... args) {

(std::cout << ... << args);

}



print(1.7, -2, "qwe");



// Output: 1.7-2qwe




Уже лучше. Точнее не так. Проще не бывает уже)

Как видите, здесь я использую бинарный левый фолд. В качестве инициализатора выступает стандартный поток вывода и он слева не только потому, что так обычно принято, а потому что оператор << также применяется например для бинарного сдвига. И чтобы мы всегда именно в поток писали, нужно, чтобы слева всегда был нужный поток. Тогда будет вызываться соответствующая перегрузка для ostream'ов и каждый раз будет возвращаться ссылка на этот поток. Таким образом мы и будем продолжать писать именно в него.



Но как тут быть с форматингом? args тут просто раскроются в последовательность "arg1 << arg2 << arg3" и тд

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



1️⃣ Не обязательно использовать сырой пакет параметров. Можно использовать функцию, принимающую этот пак.

2️⃣ Применяя оператор запятую, мы можем в операндах выполнять любое выражение, даже возвращающее void.



Получается такая штука:



template<typename ...Args>

void print(Args&&... args) {

auto print_with_space = [](const auto& v) { std::cout << v << ' '; };

(print_with_space(args), ... , (std::cout << std::endl));

}



print(1.7, -2, "qwe");

print("You, "are", "the", "best!!");



// Output:

// 1.7 -2 qwe

// You are the best!!




Здесь мы за счет лямбды и запятой выполняем каждый раз отдельную операцию вывода в поток с пробелом. А затем вместо init выражения подставляем вывод конца строки.



Прикольный хак, я считаю! В нем много нюансов, но его явно можно взять себе на вооружение и использовать в специфичных кейсах.



Hack this life. Stay cool.



#cpp17 #template