Jest to kontynuacja mojego poprzedniego pytania o ładnie zadrukowane pojemniki STL , dla których udało nam się opracować bardzo eleganckie iw pełni ogólne rozwiązanie.
W następnym kroku chciałbym dołączyć drukowanie ładnie std::tuple<Args...>
przy użyciu szablonów wariadycznych (więc jest to ściśle C ++ 11). Po std::pair<S,T>
prostu mówię
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
return o << "(" << p.first << ", " << p.second << ")";
}
Jaka jest analogiczna konstrukcja do drukowania krotki?
Próbowałem rozpakować różne fragmenty stosu argumentów szablonów, przekazywać indeksy i używać SFINAE, aby dowiedzieć się, kiedy jestem na ostatnim elemencie, ale bezskutecznie. Nie będę cię obciążać moim złamanym kodem; miejmy nadzieję, że opis problemu jest wystarczająco prosty. Zasadniczo chciałbym zachować następujące zachowanie:
auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)
Dodatkowe punkty za uwzględnienie tego samego poziomu ogólności (char / wchar_t, separatory par) jak w poprzednim pytaniu!
źródło
Odpowiedzi:
Tak, indeksy ~
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 << ")"; }
Przykład na żywo w Ideone.
W przypadku ograniczników wystarczy dodać te częściowe specjalizacje:
// 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")" };
i odpowiednio zmienić
operator<<
iprint_tuple
odpowiednio: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; }
I
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)...}; }
źródło
TuplePrinter
nie ma rozszerzeniaoperator<<
.class Tuple
dooperator<<
przeciążenia - zostałby wybrany do wszystkiego. Wymagałoby to ograniczenia, co w pewnym sensie implikuje potrzebę użycia różnego rodzaju argumentów.swallow{(os << get<Is>(t))...};
.W C ++ 17 możemy to osiągnąć przy odrobinie mniejszej ilości kodu, wykorzystując wyrażenia fold , w szczególności jednoargumentowe lewe zawinięcie:
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)>()); }
Wyjścia Live Demo :
dany
auto a = std::make_tuple(5, "Hello", -0.1); print(a);
Wyjaśnienie
Nasza jednoargumentowa lewa fałda ma postać
gdzie
op
w naszym scenariuszu jest operatorem przecinka ipack
jest wyrażeniem zawierającym naszą krotkę w nierozwiniętym kontekście, takim jak:(..., (std::cout << std::get<I>(myTuple))
Więc jeśli mam taką krotkę:
auto myTuple = std::make_tuple(5, "Hello", -0.1);
A
std::integer_sequence
którego wartości są określone przez szablon inny niż typ (patrz kod powyżej)size_t... I
Potem wyrażenie
(..., (std::cout << std::get<I>(myTuple))
Rozszerza się do
((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));
Który zostanie wydrukowany
Co jest obrzydliwe, więc musimy zrobić więcej sztuczek, aby dodać separator przecinka, który zostanie wydrukowany jako pierwszy, chyba że jest to pierwszy element.
Aby to osiągnąć, modyfikujemy
pack
część wyrażenia zwijania, aby była drukowana," ,"
jeśli bieżący indeksI
nie jest pierwszym, stąd(I == 0? "" : ", ")
część * :(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
A teraz dostaniemy
Co wygląda ładniej (uwaga: chciałem uzyskać podobny wynik jak ta odpowiedź )
* Uwaga: Możesz zrobić separację przecinków na różne sposoby niż to, z czym skończyłem. Początkowo dodałem przecinki warunkowo po, a nie przed , testując przeciwko
std::tuple_size<TupType>::value - 1
, ale to było za długie, więc zamiast tego testowałem przeciwkosizeof...(I) - 1
, ale w końcu skopiowałem Xeo i skończyło się na tym, co mam.źródło
if constexpr
do przypadku podstawowego.Mam to działa dobrze w C ++ 11 (gcc 4.7). Są pewne pułapki, których nie rozważałem, ale myślę, że kod jest łatwy do odczytania i nie jest skomplikowany. Jedyną rzeczą, która może być dziwna, jest struktura "guard" tuple_printer, która zapewnia, że zakończymy działanie, gdy osiągnięty zostanie ostatni element. Inną dziwną rzeczą może być sizeof ... (Types), które zwraca liczbę typów w paczce typów. Służy do określenia indeksu ostatniego elementu (rozmiar ... (Typy) - 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; }
źródło
std::make_tuple()
. ale w momencie drukowania tegomain()
, wyrzuca kilka błędów !, Jakieś sugestie dotyczące prostszego sposobu drukowania krotek?Dziwię się, że implementacja cppreference nie została jeszcze opublikowana, więc zrobię to dla potomności. Jest ukryty w dokumencie,
std::tuple_cat
więc nie jest łatwo go znaleźć. Używa struktury ochronnej, takiej jak niektóre inne rozwiązania tutaj, ale myślę, że ich rozwiązanie jest ostatecznie prostsze i łatwiejsze do naśladowania.#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
I test:
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); }
Wynik:
Live Demo
źródło
Oparty na kodzie AndyG, dla 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; }
z wyjściem:
deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
źródło
Na podstawie przykładu w języku programowania C ++ autorstwa Bjarne Stroustrup, strona 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; }
Wynik:
() ("One meatball") (1, 1.2, "Tail!")
źródło
Wykorzystując
std::apply
(C ++ 17) możemy porzucićstd::index_sequence
i zdefiniować jedną funkcję:#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; }
Lub lekko ozdobione za pomocą struny:
#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(); }
źródło
Kolejny, podobny do @Tony'ego Olssona, zawierający specjalizację dla pustej krotki, zgodnie z sugestią @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 << ")"; }
źródło
Podoba mi się odpowiedź DarioP, ale stringstream używa sterty. Można tego uniknąć:
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; }
źródło
Jedną rzeczą, której nie lubię w poprzednich odpowiedziach, które używają wyrażeń zwinięcia, jest to, że używają sekwencji indeksu lub flag do śledzenia pierwszego elementu, co usuwa wiele korzyści z ładnych, czystych wyrażeń zwiniętych.
Oto przykład, który nie wymaga indeksowania, ale daje podobny wynik. (Nie tak wyrafinowane jak niektóre inne, ale można by dodać więcej).
Technika polega na użyciu tego, co już daje Ci zagięcie: specjalny przypadek na jeden element. To znaczy jeden element zwinięty po prostu się rozwija
elem[0]
, a następnie są 2 elementyelem[0] + elem[1]
, gdzie+
jest jakaś operacja. Chcemy, aby jeden element zapisywał tylko ten element do strumienia, a dla większej liczby elementów robił to samo, ale łączył każdy z dodatkowym zapisem „,”. Więc mapując to na fold c ++, chcemy, aby każdy element był akcją zapisywania jakiegoś obiektu w strumieniu. Chcemy, aby nasza+
operacja polegała na przecinaniu dwóch zapisów za pomocą „,”. Więc najpierw przekształć naszą sekwencję krotek w sekwencję akcji zapisu,CommaJoiner
nazwałem ją, a następnie dla tej akcji dodajoperator+
znak, aby połączyć dwie akcje w sposób, w jaki chcemy, dodając „,” pomiędzy:#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; }
Pobieżne spojrzenie na godbolt sugeruje, że to też dobrze się komponuje, a wszystkie krzyki są spłaszczone.
Będzie to jednak wymagało drugiego przeciążenia, aby poradzić sobie z pustą krotką.
źródło