Esta é uma continuação da minha pergunta anterior sobre contêineres STL de impressão bonita , para a qual conseguimos desenvolver uma solução muito elegante e totalmente geral.
Nesta próxima etapa, gostaria de incluir a impressão bonita para std::tuple<Args...>
, usando modelos variadic (portanto, é estritamente C ++ 11). Pois std::pair<S,T>
, eu simplesmente digo
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
return o << "(" << p.first << ", " << p.second << ")";
}
Qual é a construção análoga para imprimir uma tupla?
Eu tentei vários pedaços de desempacotamento da pilha de argumentos do template, passando índices e usando SFINAE para descobrir quando estou no último elemento, mas sem sucesso. Não vou sobrecarregá-lo com meu código quebrado; a descrição do problema é esperançosamente direta o suficiente. Essencialmente, gostaria do seguinte comportamento:
auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)
Pontos de bônus por incluir o mesmo nível de generalidade (char / wchar_t, delimitadores de pares) da pergunta anterior!
fonte
Respostas:
Oba, índices ~
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 << ")"; }
Exemplo ao vivo em Ideone.
Para o delimitador, basta adicionar estas especializações parciais:
// 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")" };
e altere o
operator<<
e deprint_tuple
acordo: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; }
E
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)...}; }
fonte
TuplePrinter
não tem umoperator<<
.class Tuple
paraoperator<<
sobrecarregar - seria escolhido para todas e quaisquer coisas. Seria necessária uma restrição, o que meio que implica a necessidade de algum tipo de argumento variável.swallow{(os << get<Is>(t))...};
.Consegui fazer isso funcionando bem em C ++ 11 (gcc 4.7). Tenho certeza de que existem algumas armadilhas que não considerei, mas acho que o código é fácil de ler e não é complicado. A única coisa que pode ser estranha é a estrutura de "guarda" tuple_printer que garante que encerremos quando o último elemento for atingido. A outra coisa estranha pode ser sizeof ... (Tipos) que retorna o número de tipos no pacote de tipos de Tipos. É usado para determinar o índice do último elemento (tamanho ... (Tipos) - 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; }
fonte
std::make_tuple()
. mas na hora de imprimi-lomain()
, ele lança um monte de erros !, Alguma sugestão de maneira mais simples de imprimir as tuplas?No C ++ 17, podemos fazer isso com um pouco menos de código, aproveitando as vantagens das expressões Fold , especialmente uma dobra esquerda unária:
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)>()); }
Saídas de demonstração ao vivo :
dado
auto a = std::make_tuple(5, "Hello", -0.1); print(a);
Explicação
Nossa dobra esquerda unária é da forma
onde
op
em nosso cenário é o operador vírgula, epack
é a expressão que contém nossa tupla em um contexto não expandido como:(..., (std::cout << std::get<I>(myTuple))
Então, se eu tiver uma tupla como esta:
auto myTuple = std::make_tuple(5, "Hello", -0.1);
E um
std::integer_sequence
cujos valores são especificados por um modelo sem tipo (veja o código acima)size_t... I
Então a expressão
(..., (std::cout << std::get<I>(myTuple))
É expandido para
((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));
Que vai imprimir
O que é nojento, então precisamos fazer mais alguns truques para adicionar um separador de vírgula a ser impresso primeiro, a menos que seja o primeiro elemento.
Para fazer isso, modificamos a
pack
parte da expressão de dobra para imprimir" ,"
se o índice atualI
não for o primeiro, portanto, a(I == 0? "" : ", ")
parte * :(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
E agora vamos conseguir
O que parece melhor (Nota: eu queria uma saída semelhante a esta resposta )
* Observação: você poderia fazer a separação por vírgula de várias maneiras diferentes das que acabei fazendo. Eu inicialmente adicionei vírgulas condicionalmente depois em vez de antes testando contra
std::tuple_size<TupType>::value - 1
, mas isso era muito longo, então eu testei contrasizeof...(I) - 1
, mas no final eu copiei Xeo e acabamos com o que eu tenho.fonte
if constexpr
para o caso base.Estou surpreso que a implementação em cppreference ainda não tenha sido postada aqui, então farei isso para a posteridade. Está oculto no documento por
std::tuple_cat
isso não é fácil de encontrar. Ele usa uma estrutura de guarda como algumas das outras soluções aqui, mas acho que a delas é, em última análise, mais simples e fácil de seguir.#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
E um teste:
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); }
Resultado:
Demonstração ao vivo
fonte
Baseado no código AndyG, para 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; }
com saída:
deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
fonte
Com base no exemplo em The C ++ Programming Language de Bjarne Stroustrup, página 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; }
Resultado:
() ("One meatball") (1, 1.2, "Tail!")
fonte
Aproveitando o
std::apply
(C ++ 17), podemos abandonar ostd::index_sequence
e definir uma única função:#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; }
Ou, ligeiramente embelezado com a ajuda de um stringstream:
#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(); }
fonte
Outro, semelhante ao de @Tony Olsson, incluindo uma especialização para tupla vazia, conforme sugerido por @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 << ")"; }
fonte
Eu gosto da resposta de DarioP, mas stringstream usa heap. Isso pode ser evitado:
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; }
fonte
Uma coisa que não gosto nas respostas anteriores que usam expressões de dobra é que elas usam sequências de índice ou sinalizadores para manter o controle do primeiro elemento, o que remove muito do benefício de expressões de dobra limpa.
Aqui está um exemplo que não precisa de indexação, mas obtém um resultado semelhante. (Não tão sofisticado quanto alguns dos outros, mas mais podem ser adicionados).
A técnica é usar o que a dobra já oferece: um caso especial para um elemento. Ou seja, uma dobra de elemento apenas se expande para
elem[0]
, então 2 elementos éelem[0] + elem[1]
, onde+
há alguma operação. O que queremos é que um elemento grave apenas aquele elemento no fluxo e, para mais elementos, faça o mesmo, mas junte cada um com uma gravação adicional de ",". Portanto, mapeando isso para a dobra c ++, queremos que cada elemento seja a ação de escrever algum objeto no fluxo. Queremos que nossa+
operação seja intercalar duas gravações com uma gravação ",". Portanto, primeiro transforme nossa sequência de tupla em uma sequência de ações de gravação,CommaJoiner
como a chamei, então, para essa ação, adicione umoperator+
para juntar duas ações da maneira que queremos, adicionando um "," entre:#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; }
Uma rápida olhada em godbolt sugere que ele compila muito bem também, todas as chamadas de thunks sendo niveladas.
No entanto, isso precisará de uma segunda sobrecarga para lidar com uma tupla vazia.
fonte