2017-05-01 18 views
19

मुझे समझ में नहीं आता कि यह क्यों काम नहीं करता है। क्या कोई ऐसा व्यक्ति जो टेम्पलेट्स और वैरिएडिक एक्सप्रेशन फोल्डिंग को समझता है, क्या हो रहा है और क्या समाधान करता है?सी ++ 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

था द्विआधारी 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

आह, मुझे दूसरा भाग मिल गया। हालांकि यह एक अग्रणी 'सीपी' प्रिंट करता है। क्या प्रत्येक तर्क के बीच केवल 'सीपी' रखने का कोई सीधा तरीका है? या क्या इसे रिकर्सन जैसे अधिक जटिल समाधान की आवश्यकता है? – 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

क्या <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 यह समझ में नहीं आता है कि यह स्ट्रीमिंग है।

आपकी अन्य समस्या पर्याप्त लैम्ब्डा नहीं है।

हम इसे ठीक कर सकते हैं।

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 के लिए एक शाब्दिक भी उपयोग किया)।

ostreamer चीजों के लिए संदर्भ कब्जा यह, << 'घ है तो उन्हें उदासीनता जब बारी में यह एक 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

मैं लैम्बडास पर एक विशेषज्ञ नहीं हूं लेकिन क्या आप, अनुमान लगा सकते हैं, लैम्ब्डा को एक फ़ंक्शन में बना सकते हैं और इसे प्रिंट फ़ंक्शन से ऊपर रख सकते हैं? या यह अधिक जटिल और/या बदतर असेंबली उत्पन्न होगा? – nickeb96

+1

@ nickeb96 अगर हम इस विचार से शुरू करते हैं तो हम इस उदाहरण को अनुकूलित करना चाहते हैं (सिर्फ इसलिए कि मेरे पास दो _unoptimized_ पैरामीटर _sep_ और _end_ हैं और मुझे यकीन नहीं है कि मुझे उन्हें कैसे संभालना है), तो हम आसानी से एक ही असेंबली प्राप्त कर सकते हैं (के अनुसार जीसीसी 7.1 के लिए) एक लैम्ब्डा के बजाय एक समारोह का उपयोग कर। देखो [इस उदाहरण पर] (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)), ...); 
} 

लैम्ब्डा सुनिश्चित करता है विभाजक केवल के बीच डाला जाता है की तरह कुछ कोशिश कर सकते हैं दो आइटम

दूसरी ओर यदि आप चाहते उपयोग 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...); 
} 
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; बनाने की जरूरत है एक पैरामीटर के साथ मामले को संभालने के लिए एक अलग निर्देश।

संबंधित मुद्दे