सुंदर-प्रिंट std :: tuple


86

यह सुंदर प्रिंटिंग एसटीएल कंटेनरों पर मेरे पिछले प्रश्न का अनुवर्ती है , जिसके लिए हम एक बहुत ही सुंदर और पूरी तरह से सामान्य समाधान विकसित करने में कामयाब रहे।


इस अगले चरण में, मैं std::tuple<Args...>वैरेडिक टेम्प्लेट का उपयोग करते हुए सुंदर-प्रिंटिंग को शामिल करना चाहूंगा (इसलिए यह कड़ाई से C ++ 11 है)। के लिए std::pair<S,T>, मैं बस कहता हूँ

std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
  return o << "(" << p.first << ", " << p.second << ")";
}

ट्यूपल की छपाई के लिए अनुरूप निर्माण क्या है?

मैंने टेम्पलेट तर्क स्टैक अनपैकिंग के विभिन्न बिट्स की कोशिश की है, चारों ओर सूचकांकों को पास करना और यह पता लगाने के लिए SFINAE का उपयोग करना कि जब मैं अंतिम तत्व पर हूं, लेकिन कोई सफलता नहीं है। मैं अपने टूटे हुए कोड के साथ आप पर बोझ नहीं डालूंगा; समस्या का वर्णन उम्मीद है कि सीधे-सीधे पर्याप्त है। अनिवार्य रूप से, मुझे निम्न व्यवहार पसंद आएगा:

auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)

सामान्य प्रश्न के समान स्तर (चार / wchar_t, जोड़ी सीमांकक) को पिछले प्रश्न के रूप में शामिल करने के लिए बोनस अंक!


क्या किसी ने यहां कोई कोड लाइब्रेरी में डाला है? या यहां तक ​​कि एक .hpp-with-everything-जिसमें कोई भी पकड़ सकता है और उपयोग कर सकता है?
ईनपोकलुम

@einpoklum: शायद cxx-prettyprint ? यही मुझे उस कोड की आवश्यकता थी।
केरेक एसबी

1
महान प्रश्न, और +1 के लिए "मैं अपने टूटे हुए कोड के साथ आप पर बोझ नहीं डालूंगा", हालांकि मुझे आश्चर्य है कि यह वास्तव में नासमझों को दूर करने में सफल रहा है "आपने क्या कोशिश की है" भीड़।
डॉन हैच

जवाबों:


78

याय, सूचकांक ~

namespace aux{
template<std::size_t...> struct seq{};

template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};

template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch,Tr>& os, Tuple const& t, seq<Is...>){
  using swallow = int[];
  (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
}
} // aux::

template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
    -> std::basic_ostream<Ch, Tr>&
{
  os << "(";
  aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());
  return os << ")";
}

आइडोन पर लाइव उदाहरण।


सीमांकक सामग्री के लिए, बस इन आंशिक विशेषज्ञता जोड़ें:

// Delimiters for tuple
template<class... Args>
struct delimiters<std::tuple<Args...>, char> {
  static const delimiters_values<char> values;
};

template<class... Args>
const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = { "(", ", ", ")" };

template<class... Args>
struct delimiters<std::tuple<Args...>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};

template<class... Args>
const delimiters_values<wchar_t> delimiters<std::tuple<Args...>, wchar_t>::values = { L"(", L", ", L")" };

और बदल operator<<और print_tupleतदनुसार:

template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
    -> std::basic_ostream<Ch, Tr>&
{
  typedef std::tuple<Args...> tuple_t;
  if(delimiters<tuple_t, Ch>::values.prefix != 0)
    os << delimiters<tuple_t,char>::values.prefix;

  print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());

  if(delimiters<tuple_t, Ch>::values.postfix != 0)
    os << delimiters<tuple_t,char>::values.postfix;

  return os;
}

तथा

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch, Tr>& os, Tuple const& t, seq<Is...>){
  using swallow = int[];
  char const* delim = delimiters<Tuple, Ch>::values.delimiter;
  if(!delim) delim = "";
  (void)swallow{0, (void(os << (Is == 0? "" : delim) << std::get<Is>(t)), 0)...};
}

@Kerrek: मैं वर्तमान में परीक्षण कर रहा हूं और अपने आप को ठीक कर रहा हूं, हालांकि मुझे Ideone पर अजीब आउटपुट मिलते हैं।
Xeo

मुझे लगता है कि आप भी धाराओं और तारों को भ्रमित कर रहे हैं। आप "std :: cout << std :: cout" के लिए कुछ लिख रहे हैं। दूसरे शब्दों में, TuplePrinterएक नहीं है operator<<
केरेक एसबी

1
@ थोमस: आप बस ओवरलोड के class Tupleलिए उपयोग नहीं कर सकते operator<<- यह किसी भी और सभी चीजों के लिए चुना जाएगा। इसके लिए एक कसौटी की जरूरत होती है, जो थोड़े-थोड़े प्रकार के तर्क वितर्क की जरूरत को पूरा करती है।
Xeo

1
@DanielFrey: यह एक हल की गई समस्या है, सूची-आरंभीकरण बाएं से दाएं क्रम की गारंटी देता है swallow{(os << get<Is>(t))...};:।
Xeo

6
@Xeo मैं के लिए अपने निगल उधार cppreference , यदि आप कोई आपत्ति नहीं है।
कब्बी

19

मुझे C ++ 11 (gcc 4.7) में यह काम ठीक लगा। मुझे यकीन है कि कुछ नुकसान मुझे नहीं पता है, लेकिन मुझे लगता है कि कोड को पढ़ना आसान है और जटिल नहीं है। केवल एक चीज जो अजीब हो सकती है वह है "गार्ड" संरचना tuple_printer जो यह सुनिश्चित करती है कि हम अंतिम तत्व तक पहुंचने पर समाप्त कर दें। दूसरी विचित्र बात साइज़ोफ़ हो सकती है ... (टाइप्स) जो टाइप टाइप पैक में कई प्रकारों की वापसी करते हैं। इसका उपयोग अंतिम तत्व (आकार ... (प्रकार) - 1) के सूचकांक को निर्धारित करने के लिए किया जाता है।

template<typename Type, unsigned N, unsigned Last>
struct tuple_printer {

    static void print(std::ostream& out, const Type& value) {
        out << std::get<N>(value) << ", ";
        tuple_printer<Type, N + 1, Last>::print(out, value);
    }
};

template<typename Type, unsigned N>
struct tuple_printer<Type, N, N> {

    static void print(std::ostream& out, const Type& value) {
        out << std::get<N>(value);
    }

};

template<typename... Types>
std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) {
    out << "(";
    tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value);
    out << ")";
    return out;
}

1
हाँ, जो समझदार लगता है - शायद खालीपन के लिए एक और विशेषज्ञता के साथ, पूर्णता के लिए।
केरेक एसबी

@KerrekSB, c ++ में tuples को प्रिंट करने का एक सरल तरीका नहीं है।, अजगर फ़ंक्शन में, संक्षेप में एक tuple देता है और आप c ++ में, उन्हें एक फ़ंक्शन से कई वेरिएबल्स को वापस करने के लिए, उन्हें उपयोग करके पैक करने की आवश्यकता होती है std::make_tuple()। लेकिन इसे प्रिंट करने के समय main(), यह त्रुटियों का एक गुच्छा फेंकता है!, टुपल्स को प्रिंट करने के सरल तरीके पर कोई सुझाव?
अनु

19

C ++ 17 में हम फोल्ड एक्सप्रेशन , विशेषकर एक लेफ्ट फोल्ड का लाभ उठाकर इसे थोड़े कम कोड के साथ पूरा कर सकते हैं :

template<class TupType, size_t... I>
void print(const TupType& _tup, std::index_sequence<I...>)
{
    std::cout << "(";
    (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
    std::cout << ")\n";
}

template<class... T>
void print (const std::tuple<T...>& _tup)
{
    print(_tup, std::make_index_sequence<sizeof...(T)>());
}

लाइव डेमो आउटपुट:

(5, हैलो, -0.1)

दिया हुआ

auto a = std::make_tuple(5, "Hello", -0.1);
print(a);

व्याख्या

हमारी एकतरफा लेफ्ट फोल्ड फॉर्म की है

... op pack

जहां opहमारे परिदृश्य में अल्पविराम ऑपरेटर है, और packअभिव्यक्ति है जैसे कि एक अप्रकाशित संदर्भ में हमारा टपल।

(..., (std::cout << std::get<I>(myTuple))

तो अगर मैं एक टुपल की तरह है:

auto myTuple = std::make_tuple(5, "Hello", -0.1);

और std::integer_sequenceजिनका मान एक गैर-प्रकार के टेम्पलेट द्वारा निर्दिष्ट किया गया है (ऊपर कोड देखें)

size_t... I

फिर अभिव्यक्ति

(..., (std::cout << std::get<I>(myTuple))

में विस्तारित हो जाता है

((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));

जो छपेगा

5Hello-0.1

जो स्थूल है, इसलिए हमें पहली बार मुद्रित होने के लिए अल्पविराम विभाजक जोड़ने के लिए कुछ और प्रवंचना करने की आवश्यकता है।

इसे पूरा करने के लिए, हम packगुना अभिव्यक्ति के हिस्से को प्रिंट करने के लिए संशोधित करते हैं " ,"यदि वर्तमान सूचकांक Iपहले नहीं है, इसलिए (I == 0? "" : ", ")भाग * :

(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));

और अब हम प्राप्त करेंगे

5, नमस्ते, -0.1

जो अच्छा लग रहा है (नोट: मुझे इस उत्तर के समान आउटपुट चाहिए था )

* नोट: आप अल्पविराम को अलग-अलग तरीकों से कर सकते हैं जो मैंने खत्म किया। मैं शुरू में अल्पविराम का सशर्त जोड़ा के बाद के बजाय पहले के खिलाफ परीक्षण से std::tuple_size<TupType>::value - 1है, लेकिन यह बहुत बड़ा था, इसलिए मैं बजाय के खिलाफ परीक्षण किया sizeof...(I) - 1, लेकिन अंत में मैं कॉपी किया Xeo और हम मैं क्या मिल गया है के साथ समाप्त हो गया।


1
आप if constexprआधार मामले के लिए भी उपयोग कर सकते हैं ।
केरेक एसबी

@KerrekSB: यह तय करने के लिए कि क्या अल्पविराम मुद्रित करना है? बुरा विचार नहीं है, काश यह टर्नरी में आता।
एंडीज

एक सशर्त अभिव्यक्ति पहले से ही एक संभावित स्थिर अभिव्यक्ति है, इसलिए आपके पास क्या पहले से ही अच्छा है :-)
केरेक एसबी

17

मुझे आश्चर्य है कि cppreference पर कार्यान्वयन पहले से ही यहां पोस्ट नहीं किया गया है, इसलिए मैं इसे पोस्टीरिटी के लिए करूंगा। यह डॉक्टर के लिए छिपा हुआ है std::tuple_catइसलिए इसे खोजना आसान नहीं है। यह कुछ अन्य समाधानों की तरह एक गार्ड संरचना का उपयोग करता है, लेकिन मुझे लगता है कि उनका अंततः अंततः सरल और आसान है।

#include <iostream>
#include <tuple>
#include <string>

// helper function to print a tuple of any size
template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t) 
    {
        TuplePrinter<Tuple, N-1>::print(t);
        std::cout << ", " << std::get<N-1>(t);
    }
};

template<class Tuple>
struct TuplePrinter<Tuple, 1> {
    static void print(const Tuple& t) 
    {
        std::cout << std::get<0>(t);
    }
};

template<class... Args>
void print(const std::tuple<Args...>& t) 
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
// end helper function

और एक परीक्षण:

int main()
{
    std::tuple<int, std::string, float> t1(10, "Test", 3.14);
    int n = 7;
    auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n));
    n = 10;
    print(t2);
}

आउटपुट:

(10, टेस्ट, 3.14, फू, बार, 10, टेस्ट, 3.14, 10)

लाइव डेमो


4

एंडीजी कोड के आधार पर, सी ++ 17 के लिए

#include <iostream>
#include <tuple>

template<class TupType, size_t... I>
std::ostream& tuple_print(std::ostream& os,
                          const TupType& _tup, std::index_sequence<I...>)
{
    os << "(";
    (..., (os << (I == 0 ? "" : ", ") << std::get<I>(_tup)));
    os << ")";
    return os;
}

template<class... T>
std::ostream& operator<< (std::ostream& os, const std::tuple<T...>& _tup)
{
    return tuple_print(os, _tup, std::make_index_sequence<sizeof...(T)>());
}

int main()
{
    std::cout << "deep tuple: " << std::make_tuple("Hello",
                  0.1, std::make_tuple(1,2,3,"four",5.5), 'Z')
              << std::endl;
    return 0;
}

आउटपुट के साथ:

deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)

3

C ++ प्रोग्रामिंग लैंग्वेज द्वारा बज़्ने स्ट्रॉस्ट्रप, पेज 817 पर उदाहरण के आधार पर :

#include <tuple>
#include <iostream>
#include <string>
#include <type_traits>
template<size_t N>
struct print_tuple{
    template<typename... T>static typename std::enable_if<(N<sizeof...(T))>::type
    print(std::ostream& os, const std::tuple<T...>& t) {
        char quote = (std::is_convertible<decltype(std::get<N>(t)), std::string>::value) ? '"' : 0;
        os << ", " << quote << std::get<N>(t) << quote;
        print_tuple<N+1>::print(os,t);
        }
    template<typename... T>static typename std::enable_if<!(N<sizeof...(T))>::type
    print(std::ostream&, const std::tuple<T...>&) {
        }
    };
std::ostream& operator<< (std::ostream& os, const std::tuple<>&) {
    return os << "()";
    }
template<typename T0, typename ...T> std::ostream&
operator<<(std::ostream& os, const std::tuple<T0, T...>& t){
    char quote = (std::is_convertible<T0, std::string>::value) ? '"' : 0;
    os << '(' << quote << std::get<0>(t) << quote;
    print_tuple<1>::print(os,t);
    return os << ')';
    }

int main(){
    std::tuple<> a;
    auto b = std::make_tuple("One meatball");
    std::tuple<int,double,std::string> c(1,1.2,"Tail!");
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    }

आउटपुट:

()
("One meatball")
(1, 1.2, "Tail!")

3

std::apply(C ++ 17) पर उत्तोलन हम std::index_sequenceएक फ़ंक्शन को छोड़ सकते हैं और परिभाषित कर सकते हैं :

#include <tuple>
#include <iostream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::apply([&os](auto&&... args) {((os << args << " "), ...);}, t);
  return os;
}

या, एक स्ट्रिंग की मदद से थोड़ा अलंकृत:

#include <tuple>
#include <iostream>
#include <sstream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::basic_stringstream<Ch, Tr> ss;
  ss << "[ ";
  std::apply([&ss](auto&&... args) {((ss << args << ", "), ...);}, t);
  ss.seekp(-2, ss.cur);
  ss << " ]";
  return os << ss.str();
}

1

एक और एक, @ टोंस ओल्सन के समान, खाली ट्यूपल के लिए एक विशेषज्ञता सहित, @Kerrek एसबी द्वारा सुझाया गया।

#include <tuple>
#include <iostream>

template<class Ch, class Tr, size_t I, typename... TS>
struct tuple_printer
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {
        tuple_printer<Ch, Tr, I-1, TS...>::print(out, t);
        if (I < sizeof...(TS))
            out << ",";
        out << std::get<I>(t);
    }
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, 0, TS...>
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {
        out << std::get<0>(t);
    }
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, -1, TS...>
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {}
};
template<class Ch, class Tr, typename... TS>
std::ostream & operator<<(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
{
    out << "(";
    tuple_printer<Ch, Tr, sizeof...(TS) - 1, TS...>::print(out, t);
    return out << ")";
}

0

मुझे DarioP का जवाब पसंद है, लेकिन stringstream ढेर का उपयोग करता है। इससे बचा जा सकता है:

template <class... Args>
std::ostream& operator<<(std::ostream& os, std::tuple<Args...> const& t) {
  os << "(";
  bool first = true;
  std::apply([&os, &first](auto&&... args) {
    auto print = [&] (auto&& val) {
      if (!first)
        os << ",";
      (os << " " << val);
      first = false;
    };
    (print(args), ...);
  }, t);
  os << " )";
  return os;
}

0

एक बात जो मैं पिछले उत्तरों के बारे में नापसंद करता हूं, जो कि गुना अभिव्यक्तियों का उपयोग करते हैं, यह है कि वे पहले तत्व का ट्रैक रखने के लिए सूचकांक अनुक्रमों या झंडों का उपयोग करते हैं, जो कि अच्छे स्वच्छ गुना अभिव्यक्तियों के लाभ को हटा देता है।

यहां एक उदाहरण दिया गया है जिसमें अनुक्रमण की आवश्यकता नहीं है, लेकिन एक समान परिणाम प्राप्त करता है। (अन्य लोगों की तरह परिष्कृत नहीं है, लेकिन अधिक जोड़ा जा सकता है।)

तकनीक का उपयोग करना है जो तह पहले से ही आपको देता है: एक तत्व के लिए एक विशेष मामला। यानी, एक तत्व गुना सिर्फ फैलता है elem[0], फिर 2 तत्व है elem[0] + elem[1], जहां +कुछ ऑपरेशन है। हम जो चाहते हैं, वह एक तत्व के लिए बस उस तत्व को धारा में लिखने के लिए है, और अधिक तत्वों के लिए, वही करें, लेकिन हर एक को "," के अतिरिक्त लेखन के साथ जोड़ दें। इसलिए सी ++ फोल्ड पर इसे मैप करते हुए, हम चाहते हैं कि प्रत्येक तत्व स्ट्रीम के लिए कुछ ऑब्जेक्ट लिखने की क्रिया हो। हम चाहते हैं कि हमारा +ऑपरेशन दो राइट्स को "," राइट के साथ जोड़ने के लिए हो। इसलिए पहले हमारे टपल अनुक्रम को लिखने की क्रियाओं के अनुक्रम में परिवर्तित करें, CommaJoinerमैंने इसे कॉल किया है, फिर उस क्रिया के लिए operator+हम जिस तरह से चाहते हैं, उसमें दो क्रियाओं को जोड़ने के लिए जोड़ते हैं, बीच में ":"

#include <tuple>
#include <iostream>

template <typename T>
struct CommaJoiner
{
    T thunk;
    explicit CommaJoiner(const T& t) : thunk(t) {}

    template <typename S>
    auto operator+(CommaJoiner<S> const& b) const
    {
        auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) {
            a(os);
            os << ", ";
            b(os);
        };
        return CommaJoiner<decltype(joinedThunk)>{joinedThunk};
    }

    void operator()(std::ostream& os) const
    {
        thunk(os);
    }

};

template <typename ...Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup)
{
    std::apply([&](auto ...ts) {
        return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os);

    return os;
}

int main() {
    auto tup = std::make_tuple(1, 2.0, "Hello");
    std::cout << tup << std::endl;
}

गॉडबोल्ट पर एक सरसरी निगाह डालने से पता चलता है कि यह काफी अच्छी तरह से संकलित है, सभी थंक कॉल को समतल किया जा रहा है।

हालांकि एक खाली टपल से निपटने के लिए दूसरे ओवरलोड की आवश्यकता होगी।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.