2017-05-01 90 views
19

我不明白爲什麼這不起作用。能理解模板和可變表達式摺疊的人能解釋發生了什麼,並給出可行的解決方案嗎?C++ 17可變模板摺疊

#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 <<,但它在打印時每個參數之間沒有空格,則此功能起作用。

回答

26

Th二進制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...); 
    } 
} 
+0

對於'(std :: cout << sep << args,...);'它說「表達式不允許作爲摺疊表達式的操作數」。然而,它表明我把括號放在它的一部分上,比如'((std :: cout << sep << args),...);'但是這會跳過打印第一個參數。有沒有這一行的版本可以工作? – nickeb96

+0

@ nickeb96對,忘了括號。我不確定你對其餘的意思,它不會跳過第一個論點。 – Barry

+1

啊,我得到了第二部分的工作。它打印一個領先的'sep'然而。有沒有一種簡單的方法可以在每個參數之間使用'sep'?還是需要一個更復雜的解決方案,如遞歸? – nickeb96

14

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

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

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

live wandbox example


在這種情況下,正在執行在逗號操作摺疊,導致擴展如:

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

'args <0>'實際上是做了些什麼還是僅僅是僞代碼中的一個例子? – nickeb96

+1

@ nickeb96:只是僞代碼。 –

10

你真正想要做的是:

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

live example。 (我也用sep的文字來確保我用rvalues工作)。

ostreamer捕獲對事物的引用,它是<<'d,然後將它們轉儲到<<ostream

整個過程對於編譯器來說應該是透明的,所以體面的優化器應該會消除所有涉及的事情。

3

正如其他人回答的那樣,您嘗試使用錯誤的摺疊表達式格式。 你可以使用你的目的拉姆達幫手一個非常簡單的方法:

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

這會按照預期你寫的代碼的行爲。但是,如果你想避免的第一個參數前,你可以使用以下命令:

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

我不是lambdas的專家,但假設你可以將lambda變成一個函數並把它放在打印函數的上方?或者這會更復雜和/或產生更差的組裝? – nickeb96

+1

@ nickeb96如果我們想從這個想法開始優化這個例子(僅僅因爲我有兩個_unoptimized_參數_sep_和_end_,而我不知道如何處理它們),那麼我們可以很容易地獲得相同的程序集(根據到GCC 7.1)使用函數而不是lambda。看[這個例子](https://godbolt.org/g/TGoYLT):這兩個版本可以編譯定義或不定義USE_LAMBDA,你可以看到結果是一樣的。但是,我不確定我是否回答了您的疑問,因爲我使用_sep_和_end_作爲文字。 – dodomorandi

1

你可以嘗試這樣的事情

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表達式,你可以重載模板:

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

如果你不想前/後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;一個單獨的指令來處理一個參數的情況。