开发者

How to print boost::any to a stream?

I have a Map std::map<std::string, boost::any>, which comes from the boost::program_options package. Now I would like to print the content of that map:

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

Unfortunately, that is not possible because boost::any doesn't have an operator<< defined.

What is the easiest way to print that map?

I could define my own output operator for any that automatically tries to cast each any to an int, then double, then string, etc., each time ignoring errors and trying to cast until the cast is successful and I can print as the specified type.

But there should be an easier method in Boost? I'd need something like a reverse lexical_cast...


You could use boost::spirit::hold_any instead. It's defined here:

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

and is fully compatible with boost::any. This class has two differences if compared to boost::any:

  • it utilizes the small object optimization idiom and a couple of other optimization tricks, making spirit::hold_any smaller and faster than boost::any
  • it has the streaming operators (operator<<() and operator>>()) defined, allowing to input and output a spirit::hold_any seemlessly.

The only limitation is that you can't input into an empty spirit::hold_any, but it needs to be holding a (possibly default constructed) instance of the type which is expected from the input.


If you can change boost::any to another type, you can use Boost.TypeErasure. If you ever wanted to create a type that's like any, but only supporting types that support these particular operations at compile time, then this is just for you.

#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


Unfortunately, with any the only way is to use the type() method to determine what is contained within any, then cast it with any_cast. Obviously you must have RTTI enabled, but you probably already do if you're using any:

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


Define some aux function to output to 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;
    }
}

You can define a special formatting for some types

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

or

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

Then define an output operator for boost::any where you list all types you want to try to cast and output

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

And then for a 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;
}


The list of type switches proposed in other answers can be improved with a loop over a type list using Boost MPL (see documentation of mpl::for_each and mpl::vector). The following code defines an operator<< for any boost::any that is given in the type list SupportedTypes and throws an exception otherwise.

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


I think you have to cover each possible case of objects you have to print... Or use boost::variant.

EDIT: Sorry, I thought I shall write WHY.

The reason why I think that is because, looking at any source code, it seems to rely on the fact that YOU provide the types when inserting and getting data. When you insert, data is automatically detected by the compiler, so you don't have to specify it. But when you get the data, you shall use any_cast, because you're not sure of the data type you're getting.

If it worked in a different way and data type was sure, I think that would be no need for any_cast :)

Instead, variant have a limited set of possible data types, and this information is somewhat registered, giving you the ability to iterate in a generic way a variant container.

If you need this kind of manipulation - iterating a generic set of values - I think you shall use variant.


Try using xany https://sourceforge.net/projects/extendableany/?source=directory xany class allows to add new methods to any's existing functionality. By the way there is a example in documentation which does exactly what you want.


Rather than re-writing my class to use boost::spirit::hold_any, I created a way to stream boost::any, similar to what manifest suggested, but just in one place.

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

Rather cumbersome, but it allows me to stream a boost::any variable anywhere in my code.

How about being able to construct a boost::spirit::hold_any from a boost:any?


A little late for this party, but anyone that may be interested can also use std::tuple and a std::for_each-like template that iterates over a tuple.

This is based on the answer from ingomueller.net in this thread.

I had a recent case where I created a property map (reading configuration values, mainly fundamental types, from an XML file and inserting them into an std::unordered_map, where the value type is of type any. For debugging purposes I wanted to be able to print the entire map with its keys and values along with the type of the value.

In that project I am not using Boost at all, I used my own any implementation, but its very similar to boost::any.

The insertion operator basically looks like this:

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

The for_each template looks like this (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>{});
}

With this just use the StreamInserter class or something similar shown in Ingos answer.

Hope this helps.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜