簡體   English   中英

C ++ 17 Variadic模板折疊

[英]C++17 Variadic Template Folding

我不明白為什么這不起作用。 知道模板和可變參數表達式折疊的人能解釋發生了什么並提供一個有效的解決方案嗎?

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

它應該打印出每個args,其間有一個空格,最后一個換行符。 如果你刪除sep <<它就可以工作。但是在打印時每個參數之間沒有空格。

二進制fold-expression的語法必須是以下之一:

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

這將工作,但它將打印一個尾隨空格:

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

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

直播wandbox示例


在這種情況下,正在執行逗號運算符的折疊,從而產生如下擴展:

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

你真正想做的是:

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

因為你想要(sep << args)左邊用std::cout折疊。 這不起作用,因為sep << args不知道它正在流式傳輸到std::cout或流式傳輸; <<如果左側是流,則僅流式傳輸。

簡而言之,問題是sep << args不理解它是流式傳輸。

你的另一個問題是lambda不夠。

我們可以解決這個問題

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的文字來確保我使用rvalues)。

ostreamer捕捉到事物的引用是<< “d,然后轉儲他們當輪到<<一個ostream

整個過程應該對編譯器透明,因此一個體面的優化器應該蒸發所涉及的一切。

正如其他人所回答的那樣,您正在嘗試使用錯誤的折疊表達式格式。 您可以通過一種非常簡單的方式將lambda幫助器用於您的目的:

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

你可以嘗試這樣的事情

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)), ...);
}

lambda確保分隔符僅插入兩個項目之間。

另一方面,如果你不想使用lambdas,你可以重載模板:

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

如果你不想要前導/尾隨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; 用一個參數處理case的單獨指令。

另一種方法是下一個:

#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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM