Pregunta Pretty-print std :: tuple


Esta es una continuación de mi pregunta anterior sobre bastante impresión de contenedores STLpara 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<Args...>, usando plantillas variadic (así que esto es estrictamente C ++ 11). por std::pair<S,T>, Simplemente digo

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

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

He intentado varios bits de pila 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!


74
2018-06-05 20:43


origen


Respuestas:


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

Ejemplo en vivo en Ideone.


Para el delimitador, solo agrega estas especializaciones parciales:

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

y cambiar el operator<< y print_tuple en consecuencia:

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

Y

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

74
2018-06-05 20:52



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

15
2017-07-04 14:37



Me sorprende la implementación en cppreference no ha sido publicado aquí, así que lo haré para la posteridad. Está escondido en el documento de std::tuple_cat así 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 <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

Y una prueba:

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

Salida:

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

Demo en vivo


12
2018-06-29 12:59



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

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

Demo en vivo productos:

(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

dónde 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<I>(myTuple))

Entonces si tengo una tupla como esta:

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

Y un 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<I>(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 el pack parte de la expresión de doblez para imprimir " ," si el índice actual I no es el primero, de ahí el (I == 0? "" : ", ") parte*:

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

Y ahora conseguiremos

5, hola, -0.1

Lo cual se ve mejor (Nota: quería un resultado similar al 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 en lugar de antes de probando contra std::tuple_size<TupType>::value - 1, pero eso fue demasiado largo, así que probé en cambio contra sizeof...(I) - 1, pero al final copié Xeo y terminamos con lo que tengo.


12
2017-12-15 19:05



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

Salida:

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

2
2017-11-12 23:22



y aquí hay otra implementación:

https://github.com/galaxyeye/atlas/blob/master/atlas/io/tuple.h

con el código de prueba:

https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/tuple.cpp

disfrutar :)


1
2017-08-09 08:49



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

1
2017-12-30 11:13