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

发布于 2024-09-09 00:44:30 字数 622 浏览 8 评论 0 原文

我有一个地图 std::map,它来自 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、然后是字符串等定义自己的输出运算符,每次都会忽略错误并尝试进行转换,直到转换成功为止我可以按照指定的类型打印。

但是Boost中应该有更简单的方法吧?我需要类似反向 lexical_cast 的东西...

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

森罗 2024-09-16 00:44:30

您可以使用 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,但它需要保存输入所期望的类型的实例(可能是默认构造的)。

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.

柏林苍穹下 2024-09-16 00:44:30

如果您可以将 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 上直播

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

赠佳期 2024-09-16 00:44:30

不幸的是,对于 any ,唯一的方法是使用 type() 方法来确定 any 中包含的内容,然后使用 type() 对其进行强制转换>任意广播。显然,您必须启用 RTTI,但如果您使用any,则可能已经启用了 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;
  }
  ...
}

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;
  }
  ...
}
万水千山粽是情ミ 2024-09-16 00:44:30

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

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

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;
}
冷夜 2024-09-16 00:44:30

其他答案中提出的类型开关列表可以通过使用 Boost MPL 的类型列表循环来改进(请参阅 mpl::for_eachmpl::vector)。以下代码为类型列表 SupportedTypes 中给定的任何 boost::any 定义一个运算符<<,否则抛出异常。

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

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
}
情深如许 2024-09-16 00:44:30

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

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

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

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

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

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

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.

瀞厅☆埖开 2024-09-16 00:44:30

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

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.

爱你是孤单的心事 2024-09-16 00:44:30

我没有重写我的类来使用 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:any 构造 boost::spirit::hold_any 怎么样?

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?

离鸿 2024-09-16 00:44:30

这次聚会有点晚了,但任何感兴趣的人都可以使用 std::tuple 和类似 std::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 答案中显示的类似内容

希望有所帮助。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文