web-dev-qa-db-ja.com

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
}

注:<<演算子の対応するオーバーロードは、すべてのタイプのパラメーターパックで使用できると想定されている場合があります。

27
gexicide

再帰的な呼び出しや、必要な場所でのコンマなし。

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

38
Piotr Skotnicki

C++ 17では、 折りたたみ式

コードは次のようになります。

(out << ... << args);

およびパターンexpression op...op parameter-packバイナリ左折りと呼ばれ、その定義は(((expressionop arg1) op arg2) op arg3) .... op argN

このような式ステートメントでは、外側の括弧は厳密には必要ないと思いますが、フォールド式が別の演算子のオペランドである場合は、それらが必要であるか、非常に良い考えです:)

16
M.M

通常の答えは、2つの別々のオーバーロードを定義することです。ベースケースには空のオーバーロードを使用します。

// 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 << tout << t << ','

最後の要素を通過せずに内側のみにコンマが必要な場合は、コンマを出力しない個別の1つの引数のオーバーロードが必要です。一般的なオーバーロードは、パックの前に2つの異なる引数を取ります。つまり、

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...);
}
8
Kerrek SB

パラメータパックの展開は、単純な関数呼び出しでのみ機能し、中置演算子では機能しません。したがって、_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)...);
}
_
6

私は古い質問を知っていますが、それは私の問題を大いに助けてくれました。この投稿の回答に基づいてユーティリティクラスを作成しました。結果を共有したいと思います。

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

<<演算子をオーバーロードしたり、複雑なストリーム関数を使用したりするよりも、この代替手段が好きです。その再帰的なアプローチですが、理解するのはそれほど難しくありません。

1
eloiluiz

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を処理できますが、一般的なケースではできません。特に、1回の呼び出しで複数の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でフラッシュしません(ただし、プログラムが端末で実行されている場合は flushesstd::cout)。

したがって、私にとっては、'\n'を使用するか、多分 推奨 を使用しても問題ありません。

他のIOマニピュレータを使用することはまだ可能です:

Print(std::cout, std::hex, 11, '\n');

可変テンプレートを使用してstd::stringを返すsprintfカウンターパートも実装しました:

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

これが demos です。

1
anton_rh