कृपया इस पोस्ट के अंत में अपडेट पर ध्यान दें।
अद्यतन: मैंने इस पुस्तकालय के लिए GitHub पर एक सार्वजनिक परियोजना बनाई है !
मैं एक ही टेम्पलेट रखना चाहूंगा कि एक बार और सभी के माध्यम से सभी एसटीएल कंटेनरों की सुंदर-छपाई हो operator<<
। छद्म कोड में, मैं कुछ इस तरह की तलाश कर रहा हूं:
template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
o << open;
// for (typename C::const_iterator i = x.begin(); i != x.end(); i++) /* Old-school */
for (auto i = x.begin(); i != x.end(); i++)
{
if (i != x.begin()) o << delim;
o << *i;
}
o << close;
return o;
}
अब मैंने SO पर यहाँ बहुत सारे टेम्पलेट मैजिक देखे हैं, जो मैंने कभी संभव नहीं सोचा था, इसलिए मैं सोच रहा हूँ कि कोई भी ऐसा सुझाव दे सकता है जो सभी कंटेनरों से मेल खाएगा। हो सकता है कि कुछ ट्रिट-ईश हो जो यह पता लगा सकता है कि क्या कुछ आवश्यक इटरेटर है। ?
बहुत धन्यवाद!
अद्यतन (और समाधान)
चैनल 9 पर इस समस्या को फिर से उठाने के बाद , मुझे स्वेन ग्रूट से एक शानदार जवाब मिला, जो कि थोड़ा SFINAE टाइप ट्रेसिंग के साथ मिला, पूरी तरह से सामान्य और नेस्टेबल फैशन में समस्या को हल करने के लिए प्रकट होता है। सीमांकक व्यक्तिगत रूप से विशिष्ट हो सकता है, std :: सेट के लिए एक उदाहरण विशेषज्ञता शामिल है, साथ ही कस्टम सीमांकक का उपयोग करने का एक उदाहरण है।
सहायक "wra_array ()" का उपयोग कच्चे C सरणियों को प्रिंट करने के लिए किया जा सकता है। अद्यतन: छपाई के लिए जोड़े और टुपल्स उपलब्ध हैं; डिफ़ॉल्ट सीमांकक गोल कोष्ठक हैं।
सक्षम-यदि प्रकार के लक्षण के लिए C ++ 0x की आवश्यकता है, लेकिन कुछ संशोधनों के साथ इस का C ++ 98 संस्करण बनाना संभव है। टुपल्स को वैरेडिक टेम्प्लेट की आवश्यकता होती है, इसलिए C ++ 0x।
मैंने स्वेन को यहां समाधान पोस्ट करने के लिए कहा है ताकि मैं इसे स्वीकार कर सकूं, लेकिन इस बीच मैं संदर्भ के लिए कोड खुद पोस्ट करना चाहूंगा। ( अपडेट: स्वेन ने अब अपना कोड नीचे पोस्ट किया है, जिसे मैंने स्वीकार कर लिया है। मेरा अपना कोड कंटेनर प्रकार के लक्षणों का उपयोग करता है, जो मेरे लिए काम करते हैं लेकिन गैर-कंटेनर कक्षाओं के साथ अप्रत्याशित व्यवहार का कारण बन सकते हैं जो पुनरावृत्तियां प्रदान करते हैं।)
हैडर (prettyprint.h):
#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT
#include <type_traits>
#include <iostream>
#include <utility>
#include <tuple>
namespace std
{
// Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
template<typename T, typename TTraits, typename TAllocator> class set;
}
namespace pretty_print
{
// SFINAE type trait to detect a container based on whether T::const_iterator exists.
// (Improvement idea: check also if begin()/end() exist.)
template<typename T>
struct is_container_helper
{
private:
template<typename C> static char test(typename C::const_iterator*);
template<typename C> static int test(...);
public:
static const bool value = sizeof(test<T>(0)) == sizeof(char);
};
// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public ::std::integral_constant<bool, is_container_helper<T>::value> { };
// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
typedef TChar char_type;
const TChar * prefix;
const TChar * delimiter;
const TChar * postfix;
};
// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
typedef delimiters_values<TChar> type;
static const type values;
};
// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "[", ", ", "]" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"[", L", ", L"]" };
// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters< ::std::set<T, TTraits, TAllocator>, char>::values = { "{", ", ", "}" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };
// Delimiters for pair (reused for tuple, see below)
template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters< ::std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters< ::std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };
// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = ::std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar>>
struct print_container_helper
{
typedef TChar char_type;
typedef TDelimiters delimiters_type;
typedef std::basic_ostream<TChar, TCharTraits> & ostream_type;
print_container_helper(const T & container)
: _container(container)
{
}
inline void operator()(ostream_type & stream) const
{
if (delimiters_type::values.prefix != NULL)
stream << delimiters_type::values.prefix;
for (typename T::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it)
{
if (it != beg && delimiters_type::values.delimiter != NULL)
stream << delimiters_type::values.delimiter;
stream << *it;
}
if (delimiters_type::values.postfix != NULL)
stream << delimiters_type::values.postfix;
}
private:
const T & _container;
};
// Type-erasing helper class for easy use of custom delimiters.
// Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar.
// Usage: "cout << pretty_print::custom_delims<MyDelims>(x)".
struct custom_delims_base
{
virtual ~custom_delims_base() { }
virtual ::std::ostream & stream(::std::ostream &) = 0;
virtual ::std::wostream & stream(::std::wostream &) = 0;
};
template <typename T, typename Delims>
struct custom_delims_wrapper : public custom_delims_base
{
custom_delims_wrapper(const T & t) : t(t) { }
::std::ostream & stream(::std::ostream & stream)
{
return stream << ::pretty_print::print_container_helper<T, char, ::std::char_traits<char>, Delims>(t);
}
::std::wostream & stream(::std::wostream & stream)
{
return stream << ::pretty_print::print_container_helper<T, wchar_t, ::std::char_traits<wchar_t>, Delims>(t);
}
private:
const T & t;
};
template <typename Delims>
struct custom_delims
{
template <typename Container> custom_delims(const Container & c) : base(new custom_delims_wrapper<Container, Delims>(c)) { }
~custom_delims() { delete base; }
custom_delims_base * base;
};
} // namespace pretty_print
template <typename TChar, typename TCharTraits, typename Delims>
inline std::basic_ostream<TChar, TCharTraits> & operator<<(std::basic_ostream<TChar, TCharTraits> & stream, const pretty_print::custom_delims<Delims> & p)
{
return p.base->stream(stream);
}
// Template aliases for char and wchar_t delimiters
// Enable these if you have compiler support
//
// Implement as "template<T, C, A> const sdelims::type sdelims<std::set<T,C,A>>::values = { ... }."
//template<typename T> using pp_sdelims = pretty_print::delimiters<T, char>;
//template<typename T> using pp_wsdelims = pretty_print::delimiters<T, wchar_t>;
namespace std
{
// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream,
const ::pretty_print::print_container_helper<T, TChar, TCharTraits, TDelimiters> & helper)
{
helper(stream);
return stream;
}
// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
inline typename enable_if< ::pretty_print::is_container<T>::value, basic_ostream<TChar, TCharTraits>&>::type
operator<<(basic_ostream<TChar, TCharTraits> & stream, const T & container)
{
return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container);
}
// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const pair<T1, T2> & value)
{
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix;
stream << value.first;
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter;
stream << value.second;
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix;
return stream;
}
} // namespace std
// Prints a tuple to the stream using delimiters from delimiters<std::pair<tuple_dummy_t, tuple_dummy_t>>.
namespace pretty_print
{
struct tuple_dummy_t { }; // Just if you want special delimiters for tuples.
typedef std::pair<tuple_dummy_t, tuple_dummy_t> tuple_dummy_pair;
template<typename Tuple, size_t N, typename TChar, typename TCharTraits>
struct pretty_tuple_helper
{
static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value)
{
pretty_tuple_helper<Tuple, N - 1, TChar, TCharTraits>::print(stream, value);
if (delimiters<tuple_dummy_pair, TChar>::values.delimiter != NULL)
stream << delimiters<tuple_dummy_pair, TChar>::values.delimiter;
stream << std::get<N - 1>(value);
}
};
template<typename Tuple, typename TChar, typename TCharTraits>
struct pretty_tuple_helper<Tuple, 1, TChar, TCharTraits>
{
static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { stream << ::std::get<0>(value); }
};
} // namespace pretty_print
namespace std
{
template<typename TChar, typename TCharTraits, typename ...Args>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const tuple<Args...> & value)
{
if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix != NULL)
stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix;
::pretty_print::pretty_tuple_helper<const tuple<Args...> &, sizeof...(Args), TChar, TCharTraits>::print(stream, value);
if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix != NULL)
stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix;
return stream;
}
} // namespace std
// A wrapper for raw C-style arrays. Usage: int arr[] = { 1, 2, 4, 8, 16 }; std::cout << wrap_array(arr) << ...
namespace pretty_print
{
template <typename T, size_t N>
struct array_wrapper
{
typedef const T * const_iterator;
typedef T value_type;
array_wrapper(const T (& a)[N]) : _array(a) { }
inline const_iterator begin() const { return _array; }
inline const_iterator end() const { return _array + N; }
private:
const T * const _array;
};
} // namespace pretty_print
template <typename T, size_t N>
inline pretty_print::array_wrapper<T, N> pretty_print_array(const T (& a)[N])
{
return pretty_print::array_wrapper<T, N>(a);
}
#endif
उपयोग उदाहरण:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <map>
#include <set>
#include <array>
#include <tuple>
#include <utility>
#include <string>
#include "prettyprint.h"
// Specialization for a particular container
template<> const pretty_print::delimiters_values<char> pretty_print::delimiters<std::vector<double>, char>::values = { "|| ", " : ", " ||" };
// Custom delimiters for one-off use
struct MyDel { static const delimiters_values<char> values; };
const delimiters_values<char> MyDel::values = { "<", "; ", ">" };
int main(int argc, char * argv[])
{
std::string cs;
std::unordered_map<int, std::string> um;
std::map<int, std::string> om;
std::set<std::string> ss;
std::vector<std::string> v;
std::vector<std::vector<std::string>> vv;
std::vector<std::pair<int, std::string>> vp;
std::vector<double> vd;
v.reserve(argc - 1);
vv.reserve(argc - 1);
vp.reserve(argc - 1);
vd.reserve(argc - 1);
std::cout << "Printing pairs." << std::endl;
while (--argc)
{
std::string s(argv[argc]);
std::pair<int, std::string> p(argc, s);
um[argc] = s;
om[argc] = s;
v.push_back(s);
vv.push_back(v);
vp.push_back(p);
vd.push_back(1./double(i));
ss.insert(s);
cs += s;
std::cout << " " << p << std::endl;
}
std::array<char, 5> a{{ 'h', 'e', 'l', 'l', 'o' }};
std::cout << "Vector: " << v << std::endl
<< "Incremental vector: " << vv << std::endl
<< "Another vector: " << vd << std::endl
<< "Pairs: " << vp << std::endl
<< "Set: " << ss << std::endl
<< "OMap: " << om << std::endl
<< "UMap: " << um << std::endl
<< "String: " << cs << std::endl
<< "Array: " << a << std::endl
;
// Using custom delimiters manually:
std::cout << pretty_print::print_container_helper<std::vector<std::string>, char, std::char_traits<char>, MyDel>(v) << std::endl;
// Using custom delimiters with the type-erasing helper class
std::cout << pretty_print::custom_delims<MyDel>(v) << std::endl;
// Pairs and tuples and arrays:
auto a1 = std::make_pair(std::string("Jello"), 9);
auto a2 = std::make_tuple(1729);
auto a3 = std::make_tuple("Qrgh", a1, 11);
auto a4 = std::make_tuple(1729, 2875, std::pair<double, std::string>(1.5, "meow"));
int arr[] = { 1, 4, 9, 16 };
std::cout << "C array: " << wrap_array(arr) << std::endl
<< "Pair: " << a1 << std::endl
<< "1-tuple: " << a2 << std::endl
<< "n-tuple: " << a3 << std::endl
<< "n-tuple: " << a4 << std::endl
;
}
सुधार के लिए आगे के विचार:
अद्यतन: यह अब SO पर एक अलग प्रश्न है ! Upupdate: यह अब लागू किया गया है, Xeo के लिए धन्यवाद!std::tuple<...>
उसी तरह से उत्पादन को लागू करना है , जैसे हमारे पास हैstd::pair<S,T>
।नाम स्थान जोड़ें ताकि सहायक वर्ग वैश्विक नामस्थान में खून न बहाएं।किया हुआ- कस्टम सीमांकक वर्ग, या शायद प्रीप्रोसेसर मैक्रोज़ बनाने की सुविधा के लिए टेम्पलेट एलियासेस (या कुछ इसी तरह) जोड़ें।
हाल के अद्यतन:
- मैंने प्रिंट फ़ंक्शन में लूप के लिए एक सरल के पक्ष में कस्टम आउटपुट इटिटर को हटा दिया।
- सभी कार्यान्वयन विवरण अब
pretty_print
नाम स्थान में हैं।pretty_print_array
वैश्विक नाम स्थान में केवल वैश्विक स्ट्रीम ऑपरेटर और रैपर हैं। - नेमस्पेसिंग को
operator<<
ठीक किया ताकि अब वह सही तरीके से होstd
।
टिप्पणियाँ:
- आउटपुट इटरेटर को हटाने का मतलब है कि
std::copy()
सुंदर प्रिंटिंग प्राप्त करने के लिए उपयोग करने का कोई तरीका नहीं है । यदि यह एक वांछित विशेषता है, तो मैं सुंदर पुनरावृत्ति बहाल कर सकता हूं, लेकिन स्वेन के कोड में कार्यान्वयन है। - यह ऑब्जेक्ट कॉन्स्टेंट के बजाय सीमांकक संकलन-समय स्थिरांक बनाने के लिए एक सचेत डिजाइन निर्णय था। इसका मतलब है कि आप रनटाइम पर गतिशील रूप से डिलिमिटर्स की आपूर्ति नहीं कर सकते हैं, लेकिन इसका मतलब यह भी है कि कोई अनावश्यक ओवरहेड नहीं है। नीचे दिए गए स्वेन के कोड में एक टिप्पणी के आधार पर डेनिस ज़िकफ़ोज़ द्वारा ऑब्जेक्ट-आधारित सीमांकक कॉन्फ़िगरेशन प्रस्तावित किया गया है। यदि वांछित है, तो इसे वैकल्पिक सुविधा के रूप में लागू किया जा सकता है।
- वर्तमान में यह स्पष्ट नहीं है कि नेस्टेड कंटेनर सीमांकक को कैसे अनुकूलित किया जाए।
- इस बात को ध्यान में रखें कि इस लाइब्रेरी का उद्देश्य त्वरित कंटेनर प्रिंटिंग सुविधाओं की अनुमति देना है जो आपके हिस्से पर शून्य कोडिंग की आवश्यकता होती है । यह एक सर्व-प्रयोजन प्रारूपण पुस्तकालय नहीं है, बल्कि कंटेनर निरीक्षण के लिए बॉयलर-प्लेट कोड लिखने की आवश्यकता को कम करने के लिए एक विकासशील उपकरण है।
आप सभी को धन्यवाद जिन्होंने योगदान दिया!
नोट: यदि आप कस्टम सीमांकक को तैनात करने का एक त्वरित तरीका ढूंढ रहे हैं, तो यहां एक प्रकार का क्षरण का उपयोग किया गया है। हम मानते हैं कि आप पहले से ही एक सीमांकित वर्ग का निर्माण कर चुके हैं MyDel
, जैसे कि:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
अब हम उन सीमांकक का उपयोग करके std::cout << MyPrinter(v) << std::endl;
कुछ कंटेनर के लिए लिखना चाहते हैं v
। MyPrinter
एक प्रकार-मिटा वर्ग होगा, जैसे:
struct wrapper_base
{
virtual ~wrapper_base() { }
virtual std::ostream & stream(std::ostream & o) = 0;
};
template <typename T, typename Delims>
struct wrapper : public wrapper_base
{
wrapper(const T & t) : t(t) { }
std::ostream & stream(std::ostream & o)
{
return o << pretty_print::print_container_helper<T, char, std::char_traits<char>, Delims>(t);
}
private:
const T & t;
};
template <typename Delims>
struct MyPrinter
{
template <typename Container> MyPrinter(const Container & c) : base(new wrapper<Container, Delims>(c)) { }
~MyPrinter() { delete base; }
wrapper_base * base;
};
template <typename Delims>
std::ostream & operator<<(std::ostream & o, const MyPrinter<Delims> & p) { return p.base->stream(o); }
pretty_print
नेमस्पेस में डाल देगा और उपयोगकर्ता को प्रिंटिंग के समय उपयोग करने के लिए एक आवरण प्रदान करेगा। उपयोगकर्ता के दृष्टिकोण से: std::cout << pretty_print(v);
(शायद एक अलग नाम के साथ)। फिर आप ऑपरेटर को रैपर के समान नामस्थान में प्रदान कर सकते हैं, और फिर यह आपके इच्छित कुछ भी मुद्रण के लिए विस्तार कर सकता है। आप प्रत्येक कॉल के भीतर विभाजक को परिभाषित करने के लिए वैकल्पिक रूप से परिभाषित करने की अनुमति देने वाले आवरण को बढ़ा सकते हैं (इसके बजाय पूरे एप्लिकेशन के लिए समान विकल्प को लागू करने के लिए बाध्य करें)