web-dev-qa-db-ja.com

C ++ 17 Variadicテンプレートの折りたたみ

これがうまくいかない理由がわかりません。テンプレートと可変式の折りたたみを理解している人が何が起こっているのかを説明し、機能する解決策を提供できますか?

#include <iostream>
#include <string>

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << sep << args) << end;
}

int main()
{
    print(1, 2, 3);
}

間にスペースを入れ、最後に改行を入れて、各引数を出力する必要があります。 sep <<を削除しても機能しますが、出力時に各引数の間にスペースがありません。

25
nickeb96

バイナリの文法 fold-expressions は、次のいずれかでなければなりません。

(pack op ... op init)
(init op ... op pack)

あなたが持っているのは(std::cout << ... << sep << args)で、どちらの形式にも適合しません。 (cout << ... << pack)のようなものが必要です。これがsepの削除が機能する理由です。

代わりに、コンマで折り返すことができます。

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

または再帰を使用します:

template <class A, class... Args>
void print(A arg, Args... args) {
    std::cout << arg;
    if constexpr (sizeof...(Args) > 0) {
        std::cout << sep;
        print(args...);
    }
}
32
Barry

これは機能しますが、後続のスペースを出力します:

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";

    ((std::cout << args << sep), ...) << end;
}

ライブワンドボックスの例


この場合、カンマ演算子の畳み込みが実行されているため、次のような展開になります。

// (pseudocode)
(std::cout << args<0> << sep), 
(std::cout << args<1> << sep),
(std::cout << args<2> << sep), 
...,
(std::cout << args<N> << sep), 
15
Vittorio Romeo

あなたが本当にやりたいことは:

std::string sep = " ";
std::string end = "\n";
(std::cout << ... << (sep << args)) << end;

(sep << args)std::coutで左折したいからです。 sep << argsは、std::coutにストリーミングされているか、まったくストリーミングされているかを認識していないため、これは機能しません。 <<は、左側がストリームの場合にのみストリーミングされます。

要するに、問題はsep << argsがストリーミングであることを理解していないことです。

あなたの他の問題は十分なラムダではありません。

これを修正できます。

template<class F>
struct ostreamer_t {
    F f;
    friend std::ostream& operator<<(std::ostream& os, ostreamer_t&& self ) {
        self.f(os);
        return os;
    }
    template<class T>
    friend auto operator<<(ostreamer_t self, T&& t) {
        auto f = [g = std::move(self.f), &t](auto&& os)mutable {
            std::move(g)(os);
            os << t;
        };
        return ostreamer_t<decltype(f)>{std::move(f)};
    }
};

struct do_nothing_t {
    template<class...Args>
    void operator()(Args&&...)const {}
};

const ostreamer_t<do_nothing_t> ostreamer{{}};

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << (ostreamer << sep << args)) << end;
}

実例 。 (私はsepのリテラルも使用して、右辺値を処理するようにしました)。

ostreamer<<されたものへの参照をキャプチャし、次に<<であるときにそれらをostreamにダンプします。

このプロセス全体はコンパイラに対して透過的である必要があるため、適切なオプティマイザは関連するすべてのものを蒸発させる必要があります。

他の人が答えたように、間違ったfold-expression形式を使用しようとしています。あなたは非常に簡単な方法であなたの目的のためにラムダヘルパーを使うことができます:

template <typename... Args>
void print(Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    (std::cout << ... << streamSep(args)) << end;
}

これは、記述したコードで予想される動作に従います。ただし、最初の引数の前にsepを使用したくない場合は、以下を使用できます。

template <typename Arg, typename... Args>
void print(Arg&& arg, Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    std::cout << arg;
    (std::cout << ... << streamSep(args)) << end;
}
3
dodomorandi

別のアプローチは次のとおりです。

#include <iostream>

template<class U, class... T>
    void printSpaced(const U& u, const T&... args)
{   
     using std::cout;
     using std::endl;         

     ((cout << u) << ... << (cout << ' ', args)) << endl;   
}

このようにして、先頭/末尾のスペースを取得しません
使用法:

printSpaced(1, 2, "Hello", 4.5f); //Output 1 2 Hello 4.5 and no trailing space
2
Gerard097

あなたはこのようなものを試すことができます

template <typename... Args>
void print(Args... args)
{
  bool first = true;
  auto lambda = [&](auto param)
  {
    if( !first) std::cout << ',';
    first= false;
    return param;
  };

  ((std::cout << lambda(args)), ...);
}

ラムダは、セパレータが2つのアイテム間にのみ挿入されることを保証します。

一方、ラムダを使用したくない場合は、テンプレートをオーバーロードできます。

template<typename T>
void print(T item)
{
  std::cout << item;
}

template<typename T, typename... Args>
void print(T item, Args... args)
{
  print(item);
  std::cout << ',';
  print(args...);
}
1
eferion

先頭/末尾を付けたくない場合はsep

template <typename First, typename... Rest>
void print(First first, Rest... rest)
{
    std::string sep = " ";
    std::string end = "\n";

    std::cout << first;
    ((std::cout << sep << rest), ...);
    std::cout << end;
}

std::cout << end;を、1つのパラメーターで大文字と小文字を処理する別の命令にする必要があります。

1
Meloman