โปรดรับทราบการปรับปรุงในตอนท้ายของโพสต์นี้
อัปเดต: ฉันได้สร้างโครงการสาธารณะบน GitHubสำหรับห้องสมุดนี้แล้ว!
ผมอยากจะมีแม่แบบเดียวที่และทุกครั้งจะดูแลสวยพิมพ์ภาชนะ STL 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;
}
ตอนนี้ฉันเห็นเทมเพลตเวทย์มนตร์มากมายที่นี่เพื่อฉันไม่เคยคิดว่าจะเป็นไปได้ดังนั้นฉันจึงสงสัยว่าใครจะแนะนำสิ่งที่จะตรงกับที่เก็บทั้งหมด C. อาจมีบางอย่างที่มีลักษณะเฉพาะที่สามารถคิดได้ว่ามีตัว iterator ที่จำเป็น ?
ขอบคุณมาก!
อัปเดต (และโซลูชัน)
หลังจากพูดถึงปัญหานี้อีกครั้งในช่อง 9ฉันได้รับคำตอบที่ยอดเยี่ยมจาก Sven Groot ซึ่งเมื่อรวมกับลักษณะของ SFINAE แล้วดูเหมือนจะแก้ปัญหาได้อย่างสมบูรณ์แบบ ตัวคั่นอาจเป็นแบบเฉพาะเจาะจงตัวอย่างพิเศษสำหรับ std :: set รวมอยู่ด้วยรวมถึงตัวอย่างของการใช้ตัวคั่นแบบกำหนดเอง
ผู้ช่วย "wrap_array ()" สามารถใช้ในการพิมพ์อาร์เรย์ C ดิบ อัปเดต: มีคู่และทูเปิลสำหรับการพิมพ์; ตัวคั่นเริ่มต้นคือเครื่องหมายวงเล็บแบบกลม
คุณลักษณะการเปิดใช้งานหากต้องการประเภท C ++ 0x แต่ด้วยการปรับเปลี่ยนบางอย่างมันควรจะเป็นไปได้ที่จะทำให้รุ่น C ++ 98 นี้ Tuples ต้องการเทมเพลต Variadic ดังนั้น C ++ 0x
ฉันขอให้ Sven โพสต์วิธีแก้ปัญหาที่นี่เพื่อให้ฉันสามารถยอมรับได้ แต่ในระหว่างนี้ฉันต้องการโพสต์รหัสด้วยตนเองเพื่อการอ้างอิง ( อัปเดต: Sven ได้โพสต์รหัสของเขาด้านล่างซึ่งฉันได้รับคำตอบแล้วรหัสของฉันใช้ลักษณะของประเภทคอนเทนเนอร์ซึ่งทำงานให้ฉัน แต่อาจทำให้เกิดพฤติกรรมที่ไม่คาดคิดกับคลาสที่ไม่ใช่คอนเทนเนอร์ที่มีตัววนซ้ำ)
ส่วนหัว (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 ! อัปเดต:สิ่งนี้ได้ถูกนำไปใช้แล้วต้องขอบคุณ Xeo!std::tuple<...>
std::pair<S,T>
เพิ่มเนมสเปซเพื่อให้คลาสตัวช่วยไม่ตกในเนมสเปซส่วนกลางเสร็จสิ้น- เพิ่มชื่อแทนเทมเพลต (หรือชื่ออื่นที่คล้ายกัน) เพื่อความสะดวกในการสร้างคลาสตัวคั่นแบบกำหนดเองหรือมาโครตัวประมวลผลล่วงหน้า
อัปเดตล่าสุด:
- ฉันลบ iterator เอาท์พุทที่กำหนดเองเพื่อความง่ายในการวนซ้ำในฟังก์ชั่นการพิมพ์
- รายละเอียดการใช้งานทั้งหมดอยู่ใน
pretty_print
namespace เฉพาะผู้ให้บริการสตรีมระดับโลกและpretty_print_array
ตัดคำเท่านั้นที่อยู่ในเนมสเปซส่วนกลาง - แก้ไขการกำหนดเนมสเปซเพื่อให้
operator<<
ถูกต้องstd
แล้ว
หมายเหตุ:
- การลบตัววนซ้ำเอาต์พุตหมายความว่าไม่มีวิธีใช้
std::copy()
ในการพิมพ์สวย ฉันอาจคืนสถานะตัววนซ้ำที่สวยได้ถ้านี่เป็นคุณสมบัติที่ต้องการ แต่โค้ดของ Sven ด้านล่างมีการใช้งาน - มันเป็นการตัดสินใจออกแบบอย่างมีสติเพื่อทำให้ค่าคงที่ของเวลาคอมไพล์แทนค่าคงที่ของวัตถุ ซึ่งหมายความว่าคุณไม่สามารถจัดหาตัวคั่นแบบไดนามิกขณะรันไทม์ แต่ก็หมายความว่าไม่มีค่าใช้จ่ายที่ไม่จำเป็น การกำหนดค่าตัวคั่นเชิงวัตถุได้รับการเสนอโดย Dennis Zickefoose ในข้อคิดเห็นของรหัส Sven ด้านล่าง หากต้องการสิ่งนี้สามารถนำไปใช้เป็นคุณสมบัติเสริมได้
- ขณะนี้ยังไม่ชัดเจนว่าจะกำหนดตัวคั่นคอนเทนเนอร์ซ้อนกันได้อย่างไร
- โปรดจำไว้ว่าจุดประสงค์ของห้องสมุดนี้คือเพื่ออำนวยความสะดวกในการพิมพ์ที่รวดเร็วการเข้ารหัสในส่วนของคุณ มันไม่ได้เป็นไลบรารีการจัดรูปแบบอเนกประสงค์ แต่เป็นเครื่องมือในการพัฒนาเพื่อลดความจำเป็นในการเขียนโค้ดแผ่นบอยเลอร์สำหรับการตรวจสอบคอนเทนเนอร์
ขอบคุณทุกคนที่มีส่วนร่วม!
หมายเหตุ:หากคุณกำลังมองหาวิธีที่รวดเร็วในการปรับใช้ตัวคั่นที่กำหนดเองนี่คือวิธีหนึ่งในการลบประเภท เราคิดว่าคุณได้สร้างคลาสตัวคั่นแล้วให้พูด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);
(อาจมีชื่ออื่น) จากนั้นคุณสามารถจัดเตรียมโอเปอเรเตอร์ในเนมสเปซเดียวกันกับ wrapper และจากนั้นคุณสามารถขยายไปสู่การพิมพ์แบบสวย ๆ ที่คุณต้องการ นอกจากนี้คุณยังสามารถเพิ่มประสิทธิภาพของ wrapper ที่อนุญาตให้เลือกกำหนดตัวคั่นเพื่อใช้ภายในการโทรแต่ละครั้ง (แทนที่จะใช้ลักษณะที่บังคับให้มีตัวเลือกเดียวกันสำหรับแอปพลิเคชันทั้งหมด) \