Это продолжение моего предыдущего вопроса о хорошо печатаемых контейнерах STL , для которого нам удалось разработать очень элегантное и полностью общее решение.
На этом следующем шаге я хотел бы включить красивую печать 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)
Бонусные баллы за включение такого же уровня универсальности (char / wchar_t, разделители пар), что и в предыдущем вопросе!
источник
Ответы:
Ура, индексы ~
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)...}; }
источник
TuplePrinter
него нетoperator<<
.class Tuple
дляoperator<<
перегрузки - его выбирают для чего . Для этого потребуется ограничение, которое как бы подразумевает необходимость в каких-то вариативных аргументах.swallow{(os << get<Is>(t))...};
.В C ++ 17 мы можем добиться этого с немного меньшим количеством кода, воспользовавшись выражениями Fold , особенно унарной левой складкой:
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)>()); }
Выходы Live Demo :
дано
auto a = std::make_tuple(5, "Hello", -0.1); print(a);
Объяснение
Наша унарная левая складка имеет вид
где
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));
Что напечатает
Это грубо, поэтому нам нужно проделать еще несколько уловок, чтобы добавить разделитель запятой, который будет напечатан первым, если это не первый элемент.
Для этого мы изменяем
pack
часть выражения свертки для печати," ,"
если текущий индексI
не является первым, следовательно,(I == 0? "" : ", ")
часть * :(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
А теперь получим
Что выглядит лучше (Примечание: я хотел получить аналогичный результат, как в этом ответе )
* Примечание. Вы можете разделить запятую разными способами, чем я. Сначала я добавил запятые условно после, а не до , проверяя
std::tuple_size<TupType>::value - 1
, но это было слишком долго, поэтому я протестировал вместо этогоsizeof...(I) - 1
, но в конце я скопировал Xeo, и мы получили то, что у меня есть.источник
if constexpr
для базового случая.У меня это нормально работает на С ++ 11 (gcc 4.7). Я уверен, что есть некоторые подводные камни, которые я не учел, но я думаю, что код легко читается и не сложен. Единственное, что может показаться странным, - это «охранная» структура tuple_printer, которая гарантирует, что мы завершим работу, когда будет достигнут последний элемент. Другая странная вещь может быть sizeof ... (Типы), которые возвращают количество типов в пакете типов типов. Используется для определения индекса последнего элемента (размер ... (Типы) - 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; }
источник
std::make_tuple()
. но во время печати онmain()
выдает кучу ошибок !, Есть ли предложения по более простому способу печати кортежей?Я удивлен, что реализация 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); }
Выход:
Живая демонстрация
источник
На основе кода AndyG для C ++ 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)
источник
На основе примера на языке программирования 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!")
источник
Используя
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(); }
источник
Еще один, похожий на @Tony Olsson, включая специализацию для пустого кортежа, предложенную @Kerrek SB.
#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 << ")"; }
источник
Мне нравится ответ 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; }
источник
Что мне не нравится в предыдущих ответах, в которых используются выражения свертки, так это то, что они используют последовательности индексов или флаги для отслеживания первого элемента, что лишает многих преимуществ красивых чистых выражений свертки.
Вот пример, который не требует индексации, но дает аналогичный результат. (Не такой изощренный, как некоторые другие, но можно добавить и другие.)
Техника заключается в использовании того, что уже дает вам складка: особый случай для одного элемента. То есть, один элемент свёртка просто расширяется
elem[0]
, затем два элементаelem[0] + elem[1]
, где+
какая-то операция. Мы хотим, чтобы один элемент записывал только этот элемент в поток, а для большего количества элементов делали то же самое, но присоединялись к каждому с дополнительной записью «,». Итак, отображая это на свертке c ++, мы хотим, чтобы каждый элемент был действием записи некоторого объекта в поток. Мы хотим, чтобы наша+
операция заключалась в чередовании двух операций записи с записью «,». Итак, сначала преобразуйте нашу последовательность кортежей в последовательность действий записи, которую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; }
Беглый взгляд на Godbolt показывает, что это тоже неплохо компилируется, все звонки thunks сглаживаются.
Однако для работы с пустым кортежем потребуется вторая перегрузка.
источник