Подтвердить что ты не робот

Самый простой способ распечатать пакет переменных параметров с помощью 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
}

Примечание: Можно предположить, что соответствующая перегрузка оператора << доступна для всех типов пакета параметров.

4b9b3361

Ответ 1

Без рекурсивных вызовов и запятых, где вы хотели.

В / через расширение пакета параметров:

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


В используются выражения сгиба:

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());
}

Вот несколько демонстраций.