2010-07-11 7 views
12

Tengo un mapa std::map<std::string, boost::any>, que viene del paquete boost::program_options. Ahora me gustaría para imprimir el contenido de ese mapa:Cómo imprimir boost :: any a una secuencia?

for(po::variables_map::const_iterator it = vm.begin(); it != vm.end(); ++it) { 
    std::cerr << it->first << ": " << it->second << std::endl; 
} 

Por desgracia, eso no es posible porque boost::any no tiene un operator<< definido.

¿Cuál es la forma más fácil de imprimir ese mapa?

Podría definir mi propio operador de salida para cualquiera que automáticamente intente convertir cada any en un int, luego en doble, luego en cadena, etc., ignorando errores e intentando lanzar hasta que el reparto sea exitoso y pueda imprimir como el tipo especificado.

¿Pero debería haber un método más fácil en Boost? Necesitaría algo como un reverso lexical_cast ...

+0

¿No puedes usar 'boost :: variant'? Ese sería el método más fácil: algo como 'any' solo parece simple al principio. –

+0

Bueno, algún código externo ha producido este mapa para mí. Tal vez puedo convertirlo a 'map ' de alguna manera? – Frank

+0

Creo que viene de Boost.PO? ¿Tienes control sobre la fuente usando Boost.PO? –

Respuesta

0

Creo que tiene que cubrir cada caso posible de objetos que tiene que imprimir ... O use boost :: variant.

EDIT: Perdón, pensé que debía escribir POR QUÉ.

La razón por la que creo que es porque, al mirar cualquier código fuente, parece depender del hecho de que USTED proporciona los tipos al insertar y obtener datos. Cuando inserta, el compilador detecta automáticamente los datos, por lo que no tiene que especificarlos. Pero cuando obtiene los datos, debe usar any_cast, porque no está seguro del tipo de datos que está obteniendo.

Si funcionó de un modo y tipo de datos diferente estaba seguro, creo que habría ninguna necesidad de any_cast :)

En cambio, la variante tienen un conjunto limitado de tipos de datos posibles, y esta información es algo registrados , que le da la capacidad de repetir de forma genérica un contenedor de variantes.

Si necesita este tipo de manipulación, iterando un conjunto genérico de valores, creo que deberá usar la variante.

3

Desafortunadamente, con cualquier la única forma es utilizar el método tipo() para determinar lo que está contenido dentro de cualquier, a continuación, fundido con any_cast. Obviamente, debe tener activado RTTI, pero es probable que ya lo hacen si está usando cualquier :

for(po::variables_map::const_iterator it = vm.begin(); it != vm.end(); ++it) { 
    if(typeid(float) == it->second.type()) { 
     std::cerr << it->first << ": " << any_cast<float>(it->second) << std::endl; 
    } 
    else if(typeid(int) == it->second.type()) { 
     std::cerr << it->first << ": " << any_cast<int>(it->second) << std::endl; 
    } 
    ... 
} 
26

Usted podría utilizar boost::spirit::hold_any lugar. Se define aquí:

#include <boost/spirit/home/support/detail/hold_any.hpp> 

y es totalmente compatible con boost::any. Esta clase tiene dos diferencias si se compara con boost::any:

  • que utiliza el pequeño lenguaje optimización objeto y un par de otros trucos de optimización, por lo que spirit::hold_any más pequeño y más rápido que boost::any
  • tiene los operadores de transmisión (operator<<() y operator>>()) definido, lo que permite ingresar y emitir un spirit::hold_any aparentemente.

La única limitación es que no se puede entrar en un vacío spirit::hold_any, pero tiene que ser la celebración de una instancia del tipo de los que se espera de la entrada (posiblemente construida default).

+1

+1 Esta es una pequeña joya muy interesante. No debería haber ninguna razón para que alguien no pueda hacer esto. ¿Te preguntas por qué hold_any es solo una parte del espíritu porque se ocupa de un montón de repeticiones si estás escupiendo o leyendo en ellas? Nice find – manifest

+1

Es parte de Spirit solo principalmente porque no tengo ni el tiempo ni la energía para hacerlo reemplazar 'boost :: any'. – hkaiser

0

En lugar de volver a escribir mi clase para usar boost::spirit::hold_any, creé una forma de transmitir boost::any, similar a lo que manifest sugirió, pero solo en un lugar.

ostream& operator<<(ostream& _os, const boost::any& _any) 
{ 
    // only define simple type conversions 
    if (_any.type() == typeid(int)) 
    _os << boost::any_cast<int>(_any); 

    /*any other types you use...*/ 
} 

bastante engorroso, pero me permite para transmitir una variable boost::any en cualquier lugar en mi código.

¿Qué tal si se puede construir un boost::spirit::hold_any desde un boost:any?

1

Definir una función auxiliar para la salida de corriente:

template<class T> 
bool out_to_stream(std::ostream& os, const boost::any& any_value) 
{ 
    try { 
     T v = boost::any_cast<T>(any_value); 
     os << v; 
     return true; 
    } catch(boost:: bad_any_cast& e) { 
     return false; 
    } 
} 

puede definir un formato especial para algunos tipos

template<> 
bool out_to_stream<std::string>(std::ostream& os, const boost::any& any_value) 
{ 
    try { 
     std::string v(std::move(boost::any_cast<std::string>(any_value))); 
     os << "'" << v << "'"; 
     return true; 
    } catch(boost:: bad_any_cast& e) { 
     return false; 
    } 
} 

o

template<> 
bool out_to_stream<bool>(std::ostream& os, const boost::any& any_value) 
{ 
    try { 
     os << ((boost::any_cast<bool>(any_value))? "yes" : "no"); 
     return true; 
    } catch(boost:: bad_any_cast& e) { 
     return false; 
    } 
} 

A continuación, defina un operador de salida para boost::any donde enumera todos los tipos que desea tratar de emitir y salida

std::ostream& operator<<(std::ostream& os, const boost::any& any_value) 
{ 
    //list all types you want to try 
    if(!out_to_stream<int>(os, any_value)) 
    if(!out_to_stream<double>(os, any_value)) 
    if(!out_to_stream<bool>(os, any_value)) 
    if(!out_to_stream<std::string>(os, any_value)) 
     os<<"{unknown}"; // all cast are failed, an unknown type of any 
    return os; 
} 

Y a continuación, para un value_type:

std::ostream& operator<<(std::ostream& os, const boost::program_options::variable_value& cmdline_val) 
{ 
    if(cmdline_val.empty()){ 
     os << "<empty>"; 
    } else { 
     os<<cmdline_val.value(); 
     if(cmdline_val.defaulted()) 
      os << "(default)"; 
    } 
    return os; 
} 
5

Si usted puede cambiar a otro tipo boost::any, puede utilizar Boost.TypeErasure. Si alguna vez quiso crear un tipo que sea como any, pero solo admite tipos que admitan estas operaciones particulares en tiempo de compilación, esto es solo para usted.

#include <boost/type_erasure/operators.hpp> 
#include <boost/type_erasure/any.hpp> 
#include <boost/mpl/vector.hpp> 
#include <random> 
#include <iostream> 

namespace te = boost::type_erasure; 

typedef te::any<boost::mpl::vector< 
    te::copy_constructible<>, 
    te::destructible<>, 
    te::ostreamable<> 
>> streamable_any; 

int main() 
{ 
    streamable_any i(42); 
    streamable_any d(23.5); 
    std::mt19937 mt; 
    streamable_any r(mt); 
    std::cout << i << "\n" << d << "\n" << r << "\n"; 
} 

Live On Coliru

0

La lista de parámetros de tipo propuesto en otras respuestas se puede mejorar con un bucle a través de una lista de tipos usando MPL Boost (véase la documentación de mpl::for_each y mpl::vector). El siguiente código define un operator<< para cualquier boost::any que se proporciona en la lista de tipos SupportedTypes y arroja una excepción en caso contrario.

#include <stdexcept> 
#include <iostream> 
#include <string> 

#include <cstdint> 

#include <boost/any.hpp> 
#include <boost/mpl/for_each.hpp> 
#include <boost/mpl/vector.hpp> 

class StreamInserter 
{ 
private: 
    std::ostream& os_; 
    const boost::any &v_; 
    mutable bool has_printed_; 

public: 
    struct UnsupportedType {}; 

    StreamInserter(std::ostream& os, const boost::any &v) 
     : os_(os), v_(v), has_printed_(false) {} 

    template <typename T> 
    void operator()(const T&) const 
    { 
     if (!has_printed_ && v_.type() == typeid(T)) 
     { 
      os_ << boost::any_cast<T>(v_); 
      has_printed_ = true; 
     } 
    } 

    void operator()(const UnsupportedType&) const 
    { 
     if (!has_printed_) 
      throw std::runtime_error("unsupported type"); 
    } 
}; 

std::ostream& operator<<(std::ostream& os, const boost::any& v) 
{ 
    typedef boost::mpl::vector<float, double, int8_t, uint8_t, int16_t, uint16_t, 
      int32_t, uint32_t, int64_t, uint64_t, std::string, const char*, 
      StreamInserter::UnsupportedType> SupportedTypes; 
    StreamInserter si(os, v); 
    boost::mpl::for_each<SupportedTypes>(si); 
    return os; 
} 

int main(int, char**) 
{ 
    std::cout << boost::any(42.0) << std::endl; 
    std::cout << boost::any(42) << std::endl; 
    std::cout << boost::any(42UL) << std::endl; 
    std::cout << boost::any("42") << std::endl; 
    std::cout << boost::any(std::string("42")) << std::endl; 
    std::cout << boost::any(bool(42)) << std::endl; // throws exception 
} 
0

Un poco tarde para este partido, pero cualquier persona que pueda estar interesada también puede utilizar std::tuple y una plantilla std::for_each -como que itera sobre una tupla.

Esto se basa en la respuesta de ingomueller.net en este hilo.

Tuve un caso reciente donde creé un mapa de propiedades (leyendo valores de configuración, principalmente tipos fundamentales, de un archivo XML e insertándolos en un std::unordered_map, donde el tipo de valor es del tipo any. Para la depuración quería ser capaz de imprimir el mapa entero con sus claves y valores junto con el tipo del valor.

en ese proyecto no estoy utilizando Boost en absoluto, he usado mi propia any aplicación, pero es muy similar al impulso :: cualquiera.

El operador de inserción básicamente se ve así:

template <typename TChar> 
inline std::basic_ostream<TChar>& 
operator<< (std::basic_ostream<TChar>& os, const sl::common::any& v) 
{ 
    // Types that we support with sl::common::any. 
    std::tuple< 
     float, double, bool, 
     int8_t, uint8_t, 
     int16_t, uint16_t, 
     int32_t, uint32_t, 
     int64_t, uint64_t, 
     std::wstring, const wchar_t*, 
     StreamInserter::UnsupportedType> t; 

    // Prepare ostream for printing a value of type any 
    StreamInserter si(os, v); 

    // Iterate over all types in tuple t. If the last type(UnsupportedType) is 
    // reached, given v is unsupported. 
    for_each(t, si); 
    return os; 
} 

La plantilla for_each se parece a esto (C++ 14):

template <typename Tuple, typename F, std::size_t ...Indices> 
constexpr void for_each_impl(Tuple&& tuple, F&& f, std::index_sequence<Indices...>) { 
    using swallow = int[]; 
    (void)swallow{1, 
     (f(std::get<Indices>(std::forward<Tuple>(tuple))), void(), int{})... 
    }; 
} 

template <typename Tuple, typename F> 
constexpr void for_each(Tuple&& tuple, F&& f) { 
    constexpr std::size_t N = std::tuple_size<std::remove_reference_t<Tuple>>::value; 
    for_each_impl(std::forward<Tuple>(tuple), std::forward<F>(f), 
        std::make_index_sequence<N>{}); 
} 

Con esto sólo tiene que utilizar la clase StreamInserter o algo similar se muestra en la respuesta Ingos.

Espero que esto ayude.

Cuestiones relacionadas