Pretty-print std :: tuple

Este es un seguimiento de mi pregunta anterior sobre los envases STL de impresión bonita , para lo cual logramos desarrollar una solución muy elegante y completamente general.


En este siguiente paso, me gustaría incluir impresión bonita para std::tuple , utilizando plantillas variadic (así que esto es estrictamente C ++ 11). Para std::pair , simplemente digo

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

¿Cuál es la construcción análoga para imprimir una tupla?

He intentado varios bits de stack de argumentos de plantilla desempacando, pasando índices y utilizando SFINAE para descubrir cuándo estoy en el último elemento, pero sin éxito. No te cargaré con mi código roto; la descripción del problema es, con suerte, bastante directa. Esencialmente, me gustaría el siguiente comportamiento:

 auto a = std::make_tuple(5, "Hello", -0.1); std::cout << a << std::endl; // prints: (5, "Hello", -0.1) 

¡Puntos de bonificación por incluir el mismo nivel de generalidad (char / wchar_t, delimitadores de pares) que la pregunta anterior!

Sí, í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 << ")"; } 

Ejemplo en vivo en Ideone.


Para el delimitador, solo agrega estas especializaciones parciales:

 // 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")" }; 

y cambie el operator<< y print_tuple consecuencia:

 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; } 

Y

 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)...}; } 

Me salió bien en C ++ 11 (gcc 4.7). Estoy seguro de algunos escollos que no he considerado, pero creo que el código es fácil de leer y no complicado. Lo único que puede ser extraño es la estructura de “guardia” tuple_printer que asegura que terminamos cuando se alcanza el último elemento. La otra cosa extraña puede ser sizeof … (Tipos) que devuelve la cantidad de tipos en Tipos tipo paquete. Se usa para determinar el índice del último elemento (tamaño … (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; } 

Me sorprende que la implementación en cppreference aún no se haya publicado aquí, así que lo haré para la posteridad. Está escondido en el documento para std::tuple_cat por lo que no es fácil de encontrar. Utiliza una estructura de guardia como algunas de las otras soluciones aquí, pero creo que la suya es en última instancia más simple y más fácil 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 

Y una prueba:

 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); } 

Salida:

(10, Prueba, 3.14, Foo, barra, 10, Prueba, 3.14, 10)

Demo en vivo

En C ++ 17 podemos lograr esto con un poco menos de código aprovechando las expresiones Fold , particularmente un doblez unario izquierdo:

 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()); } 

Resultados de demostración en vivo :

(5, Hola, -0.1)

dado

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

Explicación

Nuestro doblez izquierdo único es de la forma

 ... op pack 

donde op en nuestro escenario es el operador de coma, y pack es la expresión que contiene nuestra tupla en un contexto no expandido como:

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

Entonces si tengo una tupla como esta:

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

Y una std::integer_sequence cuyos valores están especificados por una plantilla sin tipo (ver el código anterior)

 size_t... I 

Entonces la expresión

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

Se expande a

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

Cuál imprimirá

5Hola-0.1

Que es asqueroso, así que tenemos que hacer un truco más para agregar un separador de comas que se imprimirá primero a menos que sea el primer elemento.

Para lograr eso, modificamos la porción del pack de la expresión fold para imprimir " ," si el índice actual I no es el primero, de ahí la porción (I == 0? "" : ", ") * :

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

Y ahora conseguiremos

5, hola, -0.1

Que se ve mejor (Nota: quería resultados similares a esta respuesta )

* Nota: Podrías hacer la separación de las comas en una variedad de formas con las que terminé. Inicialmente agregué comas condicionalmente después de en lugar de antes probando contra std::tuple_size::value - 1 , pero eso fue demasiado largo, así que probé en cambio contra sizeof...(I) - 1 , pero al final Copié a Xeo y terminamos con lo que tengo.

Basado en el ejemplo del lenguaje de progtwigción C ++ de 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; } 

Salida:

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

Otro, similar al de @Tony Olsson, que incluye una especialización para la tupla vacía, como lo sugiere @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 << ")"; }