19

我有一个 Map 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...

4

9 回答 9

30

你可以boost::spirit::hold_any改用。它在这里定义:

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

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

  • 它利用了小对象优化习惯和其他一些优化技巧,spirit::hold_anyboost::any
  • 它定义了流式运算符 ( operator<<()and operator>>()),允许spirit::hold_any无缝输入和输出。

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

于 2010-07-12T00:39:29.287 回答
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

于 2016-06-23T22:13:35.093 回答
3

不幸的是,any唯一的方法是使用type()方法来确定包含在any中的内容,然后使用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;
  }
  ...
}
于 2010-07-11T23:34:44.563 回答
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;
}
于 2015-10-13T15:38:43.937 回答
1

其他答案中提出的类型开关列表可以通过使用 Boost MPL 对类型列表进行循环来改进(参见mpl::for_each和的文档mpl::vector)。以下代码为类型列表中给出的operator<<任何内容定义了一个,否则将引发异常。boost::anySupportedTypes

#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
}
于 2017-06-16T10:11:49.503 回答
0

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

编辑:对不起,我想我应该写为什么。

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

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

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

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

于 2010-07-11T23:03:12.893 回答
0

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

于 2012-12-21T20:19:17.337 回答
0

我没有重新编写要使用的类boost::spirit::hold_any,而是创建了一种流方式boost::any,类似于清单建议的方式,但只是在一个地方。

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从 a构造 aboost:any怎么样?

于 2015-05-08T15:07:54.197 回答
0

这个聚会有点晚了,但任何可能感兴趣的人也可以使用std::tuple和一个类似std::for_each模板的迭代元组的模板。

这是基于 ingomueller.net 在此线程中的答案。

我最近有一个案例,我创建了一个属性映射(从 XML 文件中读取配置值,主要是基本类型,并将它们插入到 中std::unordered_map,其中值类型为 type 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>{});
}

有了这个,只需使用StreamInserterIngos 答案中显示的类或类似的东西。

希望这可以帮助。

于 2018-02-15T03:57:45.063 回答