如何将boost :: any打印到流中?

时间:2010-07-11 22:43:21

标签: c++ boost printing boost-any

我有一张地图std::map<std::string, boost::any>,来自boost::program_options套餐。现在我想打印该地图的内容:

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

不幸的是,这是不可能的,因为boost::any没有定义operator<<

打印该地图的最简单方法是什么?

我可以为任何自动尝试将每个any强制转换为int,然后是double,然后是string等等,每次忽略错误并尝试强制转换直到转换成功并且我可以打印为指定的类型。

但是在Boost中应该有一个更简单的方法吗?我需要像反向lexical_cast ......

这样的东西

9 个答案:

答案 0 :(得分:29)

您可以使用boost::spirit::hold_any代替。它在这里定义:

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

并与boost::any完全兼容。与boost::any相比,此类有两个不同之处:

  • 它利用小对象优化习惯用法和其他一些优化技巧,使spirit::hold_any更小,更快boost::any
  • 定义了流媒体运营商(operator<<()operator>>()),允许无条件地输入和输出spirit::hold_any

唯一的限制是您不能输入空spirit::hold_any,但它需要保存一个(可能是默认构造的)实例,该实例是从输入中预期的。

答案 1 :(得分:5)

如果您可以将boost::any更改为其他类型,则可以使用Boost.TypeErasure。如果你想创建一个类似any的类型,但只支持在编译时支持这些特定操作的类型,那么这只适合你。

#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

答案 2 :(得分:3)

不幸的是,使用任何唯一的方法是使用 type()方法来确定任何中包含的内容,然后将其投射使用 any_cast 。显然你必须启用RTTI,但如果你使用的是任何,你可能已经这样做了:

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

答案 3 :(得分:1)

定义一些辅助函数输出到流:

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

您可以为某些类型定义特殊格式

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

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

然后为boost::any定义一个输出运算符,列出要尝试转换和输出的所有类型

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

然后是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;
}

答案 4 :(得分:0)

我认为你必须涵盖每个可能需要打印的对象的情况......或者使用boost :: variant。

编辑:对不起,我以为我会写为什么。

我认为这是因为,查看任何源代码,它似乎依赖于您在插入和获取数据时提供类型的事实。插入时,编译器会自动检测数据,因此您不必指定它。但是当你获得数据时,你应该使用any_cast,因为你不确定你得到的数据类型。

如果它以不同的方式工作且数据类型确定,我认为不需要any_cast:)

相反,变体具有一组有限的可能数据类型,并且此信息在某种程度上已注册,使您能够以通用方式迭代变体容器。

如果你需要这种操作 - 迭代一组通用的值 - 我认为你应该使用变体。

答案 5 :(得分:0)

尝试使用xany https://sourceforge.net/projects/extendableany/?source=directory xany类允许向任何现有功能添加新方法。顺便提一下,文档中有一个例子可以完全满足您的需求。

答案 6 :(得分:0)

我没有重新编写我的课程以使用boost::spirit::hold_any,而是创建了一种流式传输boost::any的方法,类似于manifest建议的方式,但只是在一个地方。

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

相当繁琐,但它允许我在代码中的任何位置流式传输boost::any变量。

如何从boost::spirit::hold_any构建boost:any

答案 7 :(得分:0)

使用Boost MPL在类型列表上循环可以改进其他答案中提出的类型切换列表(请参阅mpl::for_eachmpl::vector的文档)。以下代码为类型列表operator<<中给出的任何boost::any定义SupportedTypes,否则抛出异常。

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

答案 8 :(得分:0)

对于这个派对来说有点晚了,但是任何可能感兴趣的人也可以使用std::tuplestd::for_each - 类似于迭代元组的模板。

这是基于ingomueller.net在这个帖子中的答案。

我有一个最近的案例,我创建了一个属性映射(从XML文件读取配置值,主要是基本类型,并将它们插入std::unordered_map,其中值类型为any类型出于调试目的,我希望能够使用键和值以及值的类型打印整个地图。

在那个项目中,我根本没有使用 Boost ,我使用了自己的any实现,但它与boost :: any非常相似。

插入操作符基本上如下所示:

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

for_each模板如下所示(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>{});
}

只需使用StreamInserter类或Ingos答案中显示的类似内容。

希望这有帮助。