2010-07-11 7 views
12

Ho una mappa std::map<std::string, boost::any>, che proviene dal pacchetto boost::program_options. Ora vorrei stampare il contenuto di quella mappa:Come stampare boost :: any to a stream?

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

Purtroppo, questo non è possibile perché boost::any non dispone di un operator<< definito.

Qual è il modo più semplice per stampare quella mappa?

Potrei definire il mio operatore di output per qualsiasi tentativo automatico di trasmettere ogni any a un int, quindi doppio, quindi stringa, ecc., Ogni volta ignorando errori e cercando di trasmettere fino a quando il cast ha esito positivo e posso stampare come il tipo specificato.

Ma ci dovrebbe essere un metodo più semplice in Boost? Avrei bisogno di qualcosa come un inverso lexical_cast ...

+0

Non puoi usare 'boost :: variant'? Questo sarebbe il metodo più semplice - qualcosa come "any" sembra solo semplice all'inizio. –

+0

Bene, alcuni codici esterni hanno prodotto questa mappa per me. Forse posso convertirlo in 'map ' in qualche modo? – Frank

+0

Penso che provenga da Boost.PO? Hai il controllo sulla fonte usando Boost.PO? –

risposta

0

Penso che tu debba coprire ogni possibile caso di oggetti che devi stampare ... O usare boost :: variant.

EDIT: Scusa, ho pensato di scrivere PERCHÉ.

Il motivo per cui penso sia perché, guardando qualsiasi codice sorgente, sembra basarsi sul fatto che l'utente fornisce i tipi durante l'inserimento e il recupero dei dati. Quando inserisci, i dati vengono rilevati automaticamente dal compilatore, quindi non devi specificarlo. Ma quando ottieni i dati, devi usare any_cast, perché non sei sicuro del tipo di dati che stai ricevendo.

Se ha funzionato in un modo e di dati di tipo diverso era sicuro, penso che sarebbe alcun bisogno di any_cast :)

Invece, la variante ha un insieme limitato di tipi di dati possibili, e questa informazione è in qualche modo registrati , dandoti la possibilità di scorrere in modo generico un contenitore variante.

Se hai bisogno di questo tipo di manipolazione - iterando un insieme generico di valori - penso che dovrai usare la variante.

3

Purtroppo, con qualsiasi l'unico modo è quello di utilizzare il metodo tipo() per determinare ciò che è contenuto all'interno qualsiasi, poi gettato con any_cast. Ovviamente è necessario avere abilitato RTTI, ma probabilmente già fare se si sta utilizzando qualsiasi :

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

Si potrebbe utilizzare boost::spirit::hold_any invece. E 'definita qui:

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

ed è pienamente compatibile con boost::any. Questa classe ha due differenze rispetto al boost::any:

  • si utilizza il piccolo linguaggio ottimizzazione oggetto e un paio di altri trucchi di ottimizzazione, rendendo spirit::hold_any più piccolo e più veloce di boost::any
  • ha gli operatori di streaming (operator<<() e operator>>() definito, che consente di immettere e produrre un spirit::hold_any in modo imprevisto.

L'unica limitazione è che non è possibile inserire in un vuoto spirit::hold_any, ma deve essere in possesso di un esempio (default eventualmente realizzato) del tipo che si prevede dall'ingresso.

+1

+1 Questa è una piccola gemma molto interessante. Non ci dovrebbe essere alcuna ragione per cui non essere in grado di farlo. Mi chiedo perché hold_any è solo una parte dello spirito perché si prende cura di molte regole se il tuo sputare è fuoriuscita o lo sta leggendo? Bella scoperta – manifest

+1

Fa parte dello Spirito solo principalmente perché non ho né il tempo né l'energia per farlo sostituire 'boost :: any'. – hkaiser

0

Piuttosto che riscrivere la mia classe per utilizzare boost::spirit::hold_any, ho creato un modo per lo streaming boost::any, simile a quello suggerito da manifest, ma solo in un unico punto.

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...*/ 
} 

piuttosto ingombrante, ma mi permette di streaming di una variabile boost::any ovunque nel mio codice.

Che ne dici di essere in grado di costruire un boost::spirit::hold_any da un boost:any?

1

Definire qualche funzione aux per l'output stream:

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

è possibile definire una formattazione speciale per alcuni tipi

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

Quindi definire un operatore di output per boost::any dove si elencano tutti i tipi che si desidera provare a trasmettere e produrre

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

E poi per 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

Se è possibile modificare boost::any ad un altro tipo, è possibile utilizzare Boost.TypeErasure. Se hai mai desiderato creare un tipo che sia come any, ma solo i tipi di supporto che supportano queste particolari operazioni in fase di compilazione, allora questo è solo per te.

#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

L'elenco dei parametri di tipo proposti in altre risposte può essere migliorata con un loop su una lista tipo utilizzando Boost MPL (vedere la documentazione di mpl::for_each e mpl::vector). Il codice seguente definisce un operator<< per qualsiasi boost::any fornito nell'elenco dei tipi SupportedTypes e genera un'eccezione in 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 po 'tardi per questo partito, ma tutti coloro che potrebbero essere interessati anche possibile utilizzare std::tuple e un modello std::for_each -come che scorre su una tupla.

Questo è basato sulla risposta di ingomueller.net in questa discussione.

ho avuto un caso recente in cui ho creato una mappa delle proprietà (la lettura di valori di configurazione, i tipi principalmente fondamentali, da un file XML e inserendoli in un std::unordered_map, dove il tipo di valore è di tipo any. Per scopi di debugging che volevo essere in grado di stampare l'intera mappa con le sue chiavi e valori insieme con il tipo di valore.

In quel progetto non sto usando Boost a tutti, ho usato il mio any implementazione, ma è molto simile per amplificare :: any.

L'operatore di inserimento è praticamente il seguente:

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

Il template for_each si presenta così (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 questo basta usare la classe StreamInserter o qualcosa di simile mostrato in OING risposta.

Spero che questo aiuti.