#cpp #cpp11
Есть код, который, используя variadic templates, печатает любое количество входных аргументов в поток: templatevoid print(Args&& ...args) { char compile_time_buffer[sizeof...(Args)] = { ((std::cout << args <<" "), 0)... }; std::cout<< "\n"; } Совершенно не могу понять, чем инициализируется массив compile_time_buffer. Что скрывается за синтаксисом ((std::cout << args <<" "), 0)? И почему массив char, а в поток корректно выводятся объекты любого типа?
Ответы
Ответ 1
В этом выражении ((std::cout << args <<" "), 0)... используется оператор запятая. Значением выражения является второй операнд после запятой, то есть 0. В результате символьный массив инициализируется нулями. При этом имеется побочный эффект вычисления первого операнда оператора запятая в виде вывода в поток переданных в функцию аргументов. Чтобы было более наглядно, то просто замените 0, например, на символ 'A'. Ниже показана демонстрационная программа. #includetemplate void print( Args && ...args ) { char compile_time_buffer[sizeof...(Args)] = { ( (std::cout << args <<" "), 'A' )... }; std::cout.write( compile_time_buffer, sizeof...(Args) ); } int main() { print( 1, 2, 3 ); return 0; } Ее вывод на консоль следующий 1 2 3 AAA Можно сделать эту программу более интересной. Например, #include template void print( Args && ...args ) { char c = 'A'; char compile_time_buffer[sizeof...(Args) + 1] = { ( std::cout << args <<" ", c++ )... }; std::cout << compile_time_buffer << std::endl; } int main() { print( 1, 2, 3 ); return 0; } Ее вывод на консоль следующий 1 2 3 ABC Вот еще один простой пример использования оператора запятая при инициализации переменной int x = ( std::cout << "Инициализация x. x = ", 10 ); std::cout << x << std::endl; На консоль будет выведено Инициализация x. x = 10 Ответ 2
В данном примере инициализация массива compile_time_buffer через список инициализации с фигурными скобками позволяет реализовать шаблонную функцию с переменным кол-вом аргументов без явного рекурсивного вызова. Подобный пример, озаглавленный как "Braced init lists", можно найти на cppreference.com. Про действие запятой уже подробно рассказано в другом ответе, я же хочу добавить, что простой тип, такой как int или char нужен для того, чтобы позволить писать выражения любого типа перед запятой, и добиваться этим самым необходимых (полезных) действий. В данном случае - это вывод на печать. Но если бы тип выражения перед запятой позволял бы помещения его в массив, то запятая и ноль после неё были бы не нужны. В данном случае выражение (std::cout << args <<" ") имеет тип std::ostream&, а как известно в C++ нельзя создать массив ссылок и объекты типа std::ostream не допускают копирование. Дополнительно имеются некоторые замечания по коду: по Стандарту не допускается сужение типа при заполнении массива через initializer_list. Т.е. либо 0 (int) нужно заменить на '\0' (char), либо изменить тип массива на int. Если бы всё выражение было бы константой времени компиляции, то проблемы бы не было (т.е. целый 0 вполне себе представим в типе char). Но из-за того, что левая часть выражения (до запятой) не является такой константой будет иметь место предупреждение. Размер массива не требуется явно задавать при инициализации, он будет вычислен автоматически на основании кол-ва элементов в фигурных скобках. Как таковой массив по факту не используется (но требуется для правильной раскрутки пакета параметров шаблона), поэтому для исключения предупреждений типа unused variable стоит добавить приведение к void. Итоговый пример может выглядеть так: #includetemplate void print(Args&& ...args) { int unused[] = { ((std::cout << args << " "), 0 )... }; std::cout << "\n"; static_cast (unused); } int main() { print(1, "a", 100.500); }
Комментариев нет:
Отправить комментарий