2

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, ' ') << "}";

This prints, for example

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

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?

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • 5
    Don't use `boost::any` then. If you want type-erasure for "printable" types, you need to stick the printing logic into the type eraser. I think there's an experimental "Boost.TypeErasure" (perhaps not in Boost itself) that allows you to synthesise type erasing classes for particular purposes. – Kerrek SB Jan 08 '16 at 09:47
  • 1
    You might be able to get away with just `long double`, `std::maxint_t`, and `std::maxuint_t` to cover all the built in types. But as Kerrek says, if you want general printing, you need it to be in your type eraser. – Martin Bonner supports Monica Jan 08 '16 at 09:57
  • @MartinBonner: What do you mean by "getting away with"? How would that code look? – Kerrek SB Jan 08 '16 at 10:17
  • I'm not that familiar with boost::any, but can you ask it "can you convert the enclosed object to a `std::maxuint_t`? If so, please give it to me?". With a bit of care, you could cover unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long, and all other "unsigned" types with one case. Hmm. Not sure - you can convert a float into an unsigned long. – Martin Bonner supports Monica Jan 08 '16 at 10:21
  • 1
    @MartinBonner: No, nothing of that sort is possible. Any cannot "convert" anything because it doesn't know what it's holding. *You* must tell it what you think it's holding! – Kerrek SB Jan 08 '16 at 10:23
  • @MartinBonner that would maybe work with simple variables, but what if you use classes as type to be substituted in the any? – Mehdi Sep 06 '17 at 11:42

2 Answers2

3

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 << '\n';
    }
}

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

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 << '\n';
    }
}

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.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • 1
    Here's the corresponding example in [Boost.TypeErasure](http://www.boost.org/doc/html/boost_typeerasure/examples.html#boost_typeerasure.examples.printf). – Kerrek SB Jan 08 '16 at 10:51
  • @KerrekSB: cool - thanks for that. Also stumbled across [this answer](http://stackoverflow.com/a/4985656/410767), which does what I describe in my final paragraph.... – Tony Delroy Jan 08 '16 at 10:57
  • Yeah - all those approaches suffer from requiring one separate dynamic dispatch *per feature*. With Boost.TypeErasure, you create a custom type that has *all* the desired features and you only need one single dynamic dispatch. – Kerrek SB Jan 08 '16 at 13:16
  • @KerrekSB: I've not looked at TypeErasure before, and am a bit confused by your statement above. If by "feature" you mean something like supporting prefix increment, streaming or addition, then - as far as I can see - you only want to do one of those at a time and it's reasonable to do whichever it is through dynamic dispatch: a virtual function for each works just fine. That's what my last paragraph's about. 15 years ago I'd use macros to mix in the different operations desired - these days not using variadic templates would be silly., so TypeErasure's a solid recommendation. – Tony Delroy Jan 08 '16 at 13:29
  • 2
    @KerrekSB I wouldn't call that a "corresponding example". Something like [this](http://coliru.stacked-crooked.com/a/ff8c2f00454f84e6) maybe. – llonesmiz Jan 08 '16 at 14:24
  • @cv_and_he: Yes, thanks. I was pretty sloppy there. That's a fine example. Perhaps to the point where you should make it an answer. – Kerrek SB Jan 08 '16 at 14:46
1

Since you're already using Boost you could consider boost::spirit::hold_any.

It already has pre-defined streaming operators (both operator<<() and operator>>()).

Just the embedded type must have the corresponding operator defined, but in your use context this seems to be completely safe.

Despite being in the detail namespace, hold_any is quite widespread and almost a ready-to-use boost:any substitute (e.g. Type Erasure - Part IV, Why you shouldn’t use boost::any)

A recent version of Boost is required (old versions had a broken copy assignment operator).

manlio
  • 18,345
  • 14
  • 76
  • 126