Самый простой способ распечатать пакет переменных параметров с помощью std:: ostream?
Каков самый простой способ распечатать пакет параметров, разделенный запятыми, используя std::ostream
?
Пример:
template<typename... Args>
void doPrint(std::ostream& out, Args... args){
out << args...; // WRONG! What to write here?
}
// Usage:
int main(){
doPrint(std::cout,34,"bla",15); // Should print: 34,bla,15
}
Примечание:
Можно предположить, что соответствующая перегрузка оператора <<
доступна для всех типов пакета параметров.
Ответы
Ответ 1
Без рекурсивных вызовов и запятых, где вы хотели.
В c++11/c++14 через расширение пакета параметров:
template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
out << std::forward<Arg>(arg);
using expander = int[];
(void)expander{0, (void(out << ',' << std::forward<Args>(args)), 0)...};
}
DEMO
В c++17 используются выражения сгиба:
template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
out << std::forward<Arg>(arg);
((out << ',' << std::forward<Args>(args)), ...);
}
ДЕМО 2
Ответ 2
В С++ 17 будет более простой способ (как намекнул Kerrek SB в комментариях, это фактически присутствовало в N4606, первом проекте после С++ 14), называемом fold выражениями:
Код будет выглядеть следующим образом:
(out << ... << args);
и expression op
pattern expression op
...
op parameter-pack
называется двоичной левой складкой, определение которой эквивалентно (((
expression
op arg1) op arg2) op arg3)
.... op argN
.
Я думаю, что внешние скобки не являются строго необходимыми для выражения-выражения, подобного этому, но если выражение fold является операндом другого оператора, то они либо необходимы, либо очень хорошая идея :)
Ответ 3
Обычный ответ заключается в определении двух отдельных перегрузок с пустым для базового случая:
// base case
void doPrint(std::ostream& out) {}
template <typename T, typename... Args>
void doPrint(std::ostream& out, T t, Args... args)
{
out << t; // add comma here, see below
doPrint(out, args...);
}
Конечно, в реальном коде я бы не стал копировать аргументы каждый раз и вместо этого использовал ссылки пересылки, но вы поняли идею.
Если вы хотите добавить запятую после каждого элемента, даже после последнего, просто замените out << t
на out << t << ','
.
Если вам нужны только запятые внутри, а не последний элемент, вам нужна отдельная перегрузка с одним аргументом, которая не печатает запятую, а общая перегрузка принимает два разных аргумента перед пакетом, то есть:
template <typename T>
void doPrint(std::ostream& out, T t)
{
out << t;
}
template <typename T, typename U, typename... Args>
void doPrint(std::ostream& out, T t, U u, Args... args)
{
out << t << ',';
doPrint(out, u, args...);
}
Ответ 4
Расширение пакета параметров работает только в обычных вызовах функций, а не для инфиксных операторов. Следовательно, вам нужно преобразовать синтаксис s << x
в синтаксис вызова простой функции f(s, x)
:
template<class Head>
void print_args_(std::ostream& s, Head&& head) {
s << std::forward<Head>(head);
}
template<class Head, class... Tail>
void print_args_(std::ostream& s, Head&& head, Tail&&... tail) {
s << std::forward<Head>(head);
print_args_(s, std::forward<Tail>(tail)...);
}
template<class... Args>
void print_args(Args&&... args) {
print_args_(std::cout, std::forward<Args>(args)...);
}
Ответ 5
Я знаю его старый вопрос, но он очень помог мне с моей проблемой. Я создал класс утилиты на основе ответов на этот пост, и я хотел бы поделиться своим результатом.
Учитывая, что мы используем версии C++ 11 или последние C++, этот класс предоставляет функции print и println для компоновки строк перед вызовом стандартного потока вывода и предотвращения проблем параллелизма. Это вариационные функции, которые используют шаблоны для печати разных типов данных.
Вы можете проверить его использование в проблеме производителя-потребителя на моем github: https://github.com/eloiluiz/threadsBar
Итак, вот мой код:
class Console {
private:
Console() = default;
inline static void innerPrint(std::ostream &stream) {}
template<typename Head, typename... Tail>
inline static void innerPrint(std::ostream &stream, Head const head, Tail const ...tail) {
stream << head;
innerPrint(stream, tail...);
}
public:
template<typename Head, typename... Tail>
inline static void print(Head const head, Tail const ...tail) {
// Create a stream buffer
std::stringbuf buffer;
std::ostream stream(&buffer);
// Feed input parameters to the stream object
innerPrint(stream, head, tail...);
// Print into console and flush
std::cout << buffer.str();
}
template<typename Head, typename... Tail>
inline static void println(Head const head, Tail const ...tail) {
print(head, tail..., "\n");
}
};
Мне нравится эта альтернатива лучше, чем перегрузка оператора <<
или использование сложных функций потока. Рекурсивный подход, но не так уж трудно понять.
Ответ 6
Общая форма, которая работает с std::wostream
:
template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out)
{
return out;
}
template <typename CharT, typename Traits, typename T>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t)
{
return (out << std::forward<T>(t));
}
template <typename CharT, typename Traits, typename T, typename... Args>
std::basic_ostream<CharT, Traits> &
Print(std::basic_ostream<CharT, Traits> &out, T &&t, Args &&...args)
{
return Print( Print(out, std::forward<T>(t)), std::forward<Args>(args)... );
}
Я не мог работать с std::endl
в общем случае (можно обрабатывать std::endl
в определенных случаях, например, когда это первый или последний аргумент, но не в общем случае, особенно если существует несколько std::endl
в одном вызове). Вы все равно можете использовать '\n'
или использовать std::endl
с аргументами шаблона, указанными, если вам действительно нужен std::endl
:
Print(std::cout, "hello world", std::endl<char, std::char_traits<char>>);
Различия между std::endl
и '\n'
- Если поток работает в двоичном режиме, тогда
'\n'
не преобразуется в формат окончания строки платформы, скомпилированный для него (но в текстовом режиме он все еще преобразован). -
'\n'
не очищает поток с помощью std::flush
(но он все еще сбрасывает std::cout
если программа работает на терминале)
Поэтому для меня это нормально использовать '\n'
, или, возможно, даже предпочтительнее.
По-прежнему возможно использование некоторых других манипуляторов IO:
Print(std::cout, std::hex, 11, '\n');
Я также реализовал аналогию с sprintf
которая работает с вариативными шаблонами и возвращает std::string
:
template <typename CharT = char, typename Traits = std::char_traits<CharT>, typename... Args>
std::basic_string<CharT, Traits>
SPrint(Args &&...args)
{
std::basic_stringstream<CharT, Traits> ss;
Print(ss, std::forward<Args>(args)...);
return std::move(ss.str());
}
Вот несколько демонстраций.