Pretty-print std :: tuple

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 include pretty-printing para std::tuple , usando modelos variádicos (portanto, isso é estritamente C ++ 11). Para std::pair , eu simplesmente digo

 std::ostream & operator<<(std::ostream & o, const std::pair & p) { return o << "(" << p.first << ", " << p.second << ")"; } 

Qual é a construção análoga para imprimir uma tupla?

Eu tentei vários pedaços de pilha de argumentos de modelo descompactando, passando por índices e usando SFINAE para descobrir quando estou no último elemento, mas sem sucesso. Eu não vou te sobrecarregar com o meu código quebrado; a descrição do problema é esperada o suficiente. Essencialmente, eu 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 para include o mesmo nível de generalidade (char / wchar_t, delimitadores de pares) como a pergunta anterior!

Sim, índices ~

 namespace aux{ template struct seq{}; template struct gen_seq : gen_seq{}; template struct gen_seq<0, Is...> : seq{}; template void print_tuple(std::basic_ostream& os, Tuple const& t, seq){ using swallow = int[]; (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get(t)), 0)...}; } } // aux:: template auto operator<<(std::basic_ostream& os, std::tuple const& t) -> std::basic_ostream& { os << "("; aux::print_tuple(os, t, aux::gen_seq()); return os << ")"; } 

Exemplo ao vivo no Ideone.


Para o material do delimitador, basta adicionar estas especializações parciais:

 // Delimiters for tuple template struct delimiters, char> { static const delimiters_values values; }; template const delimiters_values delimiters, char>::values = { "(", ", ", ")" }; template struct delimiters, wchar_t> { static const delimiters_values values; }; template const delimiters_values delimiters, wchar_t>::values = { L"(", L", ", L")" }; 

e altere o operator<< e print_tuple acordo:

 template auto operator<<(std::basic_ostream& os, std::tuple const& t) -> std::basic_ostream& { typedef std::tuple tuple_t; if(delimiters::values.prefix != 0) os << delimiters::values.prefix; print_tuple(os, t, aux::gen_seq()); if(delimiters::values.postfix != 0) os << delimiters::values.postfix; return os; } 

E

 template void print_tuple(std::basic_ostream& os, Tuple const& t, seq){ using swallow = int[]; char const* delim = delimiters::values.delimiter; if(!delim) delim = ""; (void)swallow{0, (void(os << (Is == 0? "" : delim) << std::get(t)), 0)...}; } 

Eu tenho isso funcionando bem em C + + 11 (gcc 4.7). 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 da “guarda” tuple_printer que garante que terminemos quando o último elemento for atingido. A outra coisa estranha pode ser sizeof … (Types) que retorna o número de tipos no tipo de pacote Types. É usado para determinar o índice do último elemento (tamanho … (Tipos) – 1).

 template struct tuple_printer { static void print(std::ostream& out, const Type& value) { out << std::get(value) << ", "; tuple_printer::print(out, value); } }; template struct tuple_printer { static void print(std::ostream& out, const Type& value) { out << std::get(value); } }; template std::ostream& operator<<(std::ostream& out, const std::tuple& value) { out << "("; tuple_printer, 0, sizeof...(Types) - 1>::print(out, value); out << ")"; return out; } 

Estou surpreso que a implementação na cppreference ainda não tenha sido postada aqui, então farei isso para a posteridade. Está escondido no documento para std::tuple_cat então não é fácil encontrá-lo. Ele usa uma estrutura de guarda como algumas das outras soluções aqui, mas acho que elas são mais simples e mais fáceis de seguir.

 #include  #include  #include  // helper function to print a tuple of any size template struct TuplePrinter { static void print(const Tuple& t) { TuplePrinter::print(t); std::cout << ", " << std::get(t); } }; template struct TuplePrinter { static void print(const Tuple& t) { std::cout << std::get<0>(t); } }; template void print(const std::tuple& t) { std::cout << "("; TuplePrinter::print(t); std::cout << ")\n"; } // end helper function 

E um teste:

 int main() { std::tuple 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); } 

Saída:

(10, teste, 3,14, Foo, bar, 10, teste, 3,14, 10)

Demonstração ao vivo

Em C ++ 17, podemos fazer isso com um pouco menos de código, aproveitando as expressões Fold , particularmente uma dobra esquerda unária:

 template void print(const TupType& _tup, std::index_sequence) { std::cout << "("; (..., (std::cout << (I == 0? "" : ", ") << std::get(_tup))); std::cout << ")\n"; } template void print (const std::tuple& _tup) { print(_tup, std::make_index_sequence()); } 

Saídas ao vivo de demonstração :

(5, olá, -0.1)

dado

 auto a = std::make_tuple(5, "Hello", -0.1); print(a); 

Explicação

Nossa dobra esquerda unária é da forma

 ... op pack 

onde op em nosso cenário é o operador vírgula e pack é a expressão que contém nossa tupla em um contexto não expandido como:

 (..., (std::cout << std::get(myTuple)) 

Então, se eu tenho uma tupla assim:

 auto myTuple = std::make_tuple(5, "Hello", -0.1); 

E um std::integer_sequence cujos valores são especificados por um modelo não-tipo (veja o código acima)

 size_t... I 

Então a expressão

 (..., (std::cout << std::get(myTuple)) 

É expandido para

 ((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple)); 

Qual vai imprimir

5Olá-0.1

O que é bruto, 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 isso, modificamos a parte do pack da expressão de dobra para imprimir " ," se o índice atual I não for o primeiro, portanto a parte (I == 0? "" : ", ") * :

 (..., (std::cout << (I == 0? "" : ", ") << std::get(_tup))); 

E agora nós vamos conseguir

5, Olá, -0,1

Que parece mais bonito (Nota: eu queria saída semelhante como esta resposta )

* Nota: Você pode fazer a separação por vírgula de várias formas diferentes do que acabei fazendo. Inicialmente, eu adicionei vírgulas condicionalmente depois, em vez de antes , testando std::tuple_size::value - 1 , mas isso foi muito longo, então eu testei em vez de sizeof...(I) - 1 , mas no final Eu copiei o Xeo e acabamos com o que eu tenho.

Baseado no exemplo na linguagem de programação C ++ por Bjarne Stroustrup, página 817 :

 #include  #include  #include  #include  template struct print_tuple{ templatestatic typename std::enable_if<(N::type print(std::ostream& os, const std::tuple& t) { char quote = (std::is_convertible(t)), std::string>::value) ? '"' : 0; os << ", " << quote << std::get(t) << quote; print_tuple::print(os,t); } templatestatic typename std::enable_if::type print(std::ostream&, const std::tuple&) { } }; std::ostream& operator<< (std::ostream& os, const std::tuple<>&) { return os << "()"; } template std::ostream& operator<<(std::ostream& os, const std::tuple& t){ char quote = (std::is_convertible::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 c(1,1.2,"Tail!"); std::cout << a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; } 

Saída:

 () ("One meatball") (1, 1.2, "Tail!") 

Outro, semelhante ao do @Tony Olsson, incluindo uma especialização para a tupla vazia, como sugerido por @Kerrek SB.

 #include  #include  template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) { tuple_printer::print(out, t); if (I < sizeof...(TS)) out << ","; out << std::get(t); } }; template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) { out << std::get<0>(t); } }; template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) {} }; template std::ostream & operator<<(std::basic_ostream & out, const std::tuple & t) { out << "("; tuple_printer::print(out, t); return out << ")"; }