带有 boost::any 的 cout 地图

2021-12-24 00:00:00 c++ boost cout

我有一个字典"std::map<std::string, boost::any>(或者std::any,如果你想要的话)可以嵌套.现在,我想显示地图.由于 boost::any 显然不能很好地与 << 配合使用,所以事情变得有点糟糕.到目前为止,我正在检查类型,转换它,并将转换通过管道传送到 cout:

I have a "dictionary" std::map<std::string, boost::any> (or std::any, if you want) that can possibly be nested. Now, I would like to display the map. Since boost::any obviously doesn't play nicely with <<, things are getting a little nasty. So far, I'm checking the type, cast it, and pipe the cast to cout:

for (const auto &p: map) {
  std::cout << std::string(indent + 2, ' ') << p.first << ": ";
  if (p.second.type() == typeid(int)) {
    std::cout << boost::any_cast<int>(p.second);
  } else if (p.second.type() == typeid(double)) {
    std::cout << boost::any_cast<double>(p.second);
  } else if (p.second.type() == typeid(std::string)) {
    std::cout << boost::any_cast<std::string>(p.second);
  } else if (p.second.type() == typeid(const char*)) {
    std::cout << boost::any_cast<const char*>(p.second);
  } else if (p.second.type() == typeid(std::map<std::string, boost::any>)) {
    show_map(
        boost::any_cast<std::map<std::string, boost::any>>(p.second),
        indent + 2
        );
  } else {
    std::cout << "[unhandled type]";
  }
  std::cout << std::endl;
}
std::cout << std::string(indent, ' ') << "}";

这个打印,例如

{
  fruit: banana
  taste: {
    sweet: 1.0
    bitter: 0.1
  }
}

不幸的是,这很难扩展.我必须为每种类型添加另一个 else if 子句(例如,floatsize_t、...),这就是为什么我对解决方案不是特别满意.

Unfortunately, this is hardly scalable. I'd have to add another else if clause for every type (e.g., float, size_t,...), which is why I'm not particularly happy with the solution.

有没有办法将上述内容推广到更多类型?

Is there a way to generalize the above to more types?

推荐答案

可以减轻(但不能消除)痛苦的一件事是将类型确定逻辑分解为一个支持函数,同时使用静态多态(特别是模板)) 对于要应用于值的操作...

One thing you can do to lessen (but not remove) the pain is to factor the type determination logic into one support function, while using static polymorphism (specifically templates) for the action to be applied to the values...

#include <iostream>
#include <boost/any.hpp>
#include <string>

struct Printer
{
    std::ostream& os_;

    template <typename T>
    void operator()(const T& t)
    {
        os_ << t;
    }
};

template <typename F>
void f_any(F& f, const boost::any& a)
{
    if (auto p = boost::any_cast<std::string>(&a)) f(*p);
    if (auto p = boost::any_cast<double>(&a))      f(*p);
    if (auto p = boost::any_cast<int>(&a))         f(*p);
    // whatever handling for unknown types...
}

int main()
{
    boost::any anys[] = { std::string("hi"), 3.14159, 27 };
    Printer printer{std::cout};
    for (const auto& a : anys)
    {
        f_any(printer, a);
        std::cout << '
';
    }
}

(只需稍加努力,您就可以在可变参数模板参数包中为每种类型完成特定于类型的测试和分派,从而简化该代码和维护列表的麻烦.或者,您可以只使用用于生成 if-cast/dispatch 语句的预处理器宏....)

(With only a smidge more effort, you could have the type-specific test and dispatch done for each type in a variadic template parameter pack, simplifying that code and the hassle of maintaining the list. Or, you could just use a preprocessor macro to churn out the if-cast/dispatch statements....)

仍然 - 如果您知道类型集,boost::variant 更合适,并且已经支持类似的操作(参见 这里).

Still - if you know the set of types, a boost::variant is more appropriate and already supports similar operations (see here).

另一种选择是在创建类型时记住"如何执行特定操作(例如打印):

Yet another option is to "memorise" how to do specific operations - such as printing - when you create your types:

#include <iostream>
#include <boost/any.hpp>
#include <string>
#include <functional>

struct Super_Any : boost::any
{
    template <typename T>
    Super_Any(const T& t)
      : boost::any(t),
        printer_([](std::ostream& os, const boost::any& a) { os << boost::any_cast<const T&>(a); })
    { }

    std::function<void(std::ostream&, const boost::any&)> printer_;
};

int main()
{
    Super_Any anys[] = { std::string("hi"), 3.14159, 27 };
    for (const auto& a : anys)
    {
        a.printer_(std::cout, a);
        std::cout << '
';
    }
}

如果你有很多操作并且想要减少内存使用,你可以让模板化构造函数创建并存储一个(抽象基类)指针,指向一个静态类型特定的类,该类派生自一个抽象接口,带有你的操作想要支持:这样你只需为每个 Super_Any 对象添加一个指针.

If you have many operations and want to reduce memory usage, you can have the templated constructor create and store a (abstract-base-class) pointer to a static-type-specific class deriving from an abstract interface with the operations you want to support: that way you're only adding one pointer per Super_Any object.

相关文章