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

20

我有一个来自 boost::program_options 包的 std::map<std::string, boost::any>,现在我想打印出该映射的内容:

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一样的东西...


你不能使用 boost::variant 吗?那会更简单 - 像 any 这样的东西只是一开始看起来简单。 - Georg Fritzsche
好的,一些外部代码已经为我生成了这张地图。也许我可以以某种方式将其转换为 map<string,variant> - Frank
我认为这是来自Boost.PO吗?您是否可以控制使用Boost.PO的源代码? - Georg Fritzsche
是的,我的代码使用了boost::program_options。它接收一个包含所有程序/命令行选项及其值的映射。所有值都是类型为boost::any的。 - Frank
但是你肯定期望参数有一些特定的输入吧?你看过如何处理特定类型的教程吗? - Georg Fritzsche
9个回答

30

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

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

spirit::hold_any完全兼容于boost::any。相比之下,这个类有两个不同之处:

  • 它利用小对象优化惯用语以及其他一些优化技巧,使得spirit::hold_anyboost::any更小更快
  • 它定义了流操作符(operator<<()operator>>()),允许无缝输入和输出spirit::hold_any

唯一的限制是你不能向空的spirit::hold_any输入,它需要保持一个(可能是默认构造的)从输入中期望的类型的实例。


1
+1 这是一个非常有趣的小宝石。没有任何理由不去尝试一下。不知道为什么 hold_any 只是 Spirit 的一部分,因为它可以处理很多样板文件,如果你将 any 输出到流中或从流中读取它们?很好的发现。 - manifest
2
这仅仅是 Spirit 的一部分,主要原因是我没有时间和精力来用它替代 boost::any - hkaiser

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

点击这里进入 Coliru 实时演示


4

很遗憾,使用any的唯一方法是使用type()方法确定其包含的内容,然后使用any_cast进行转换。显然,您必须启用RTTI,但如果您正在使用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;
  }
  ...
}

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

1
其他答案提出的类型转换列表可以通过使用Boost MPL中的类型列表循环进行改进(请参阅mpl::for_eachmpl::vector的文档)。以下代码为在类型列表SupportedTypes中给定的任何boost::any定义了一个operator<<,否则会抛出异常。
#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

与其重写我的类来使用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:any 构建一个 boost::spirit::hold_any

0
有些晚了,但是可能有兴趣的人也可以使用 std::tuple 和类似于 std::for_each 的模板来迭代一个元组。
这是基于 ingomueller.net 在这个主题中的回答。
我最近遇到一种情况,需要创建一个属性映射(从 XML 文件读取配置值,主要是基本类型,并将它们插入到一个值类型为anystd::unordered_map 中。出于调试目的,我希望能够打印整个地图及其键和值以及值的类型。
在那个项目中,我根本不使用 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回答中显示的类似的东西即可。

希望这可以帮助到您。


0

0

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

编辑:抱歉,我想我应该写一下为什么。

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

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

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

如果您需要这种操作-迭代通用值集,则应使用variant。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接