views:

73

answers:

2

Hello, I have to store a list of different boost::function objects. To provide this I'm using boost::any. I have a few functions which takes different functions signatures, pack them into any and then insert into special map with given type. Here is the code:

enum TypeEnumerator
{
    e_int,
    e_float,
    e_double
};

typedef map< string, pair<any, TypeEnumerator> > CallbackType;
CallbackType mCallbacks;

void Foo(const string &name, function<float ()> f)
{
    mCallbacks[name] = make_pair(any(f), CLASS::e_float);
}
void Foo(const string &name, function<int ()> f) { /* the same, but with e_int */ }
void Foo(const string &name, function<double ()> f) { /* the same, but with e_double */ }

Now I have in map boost function, packed into any with given type from enum, to recognize it in future. Now I have to call given functions. The casting from any won't work:

BOOST_FOREACH(CallbackType::value_type &row, mCallbacks)
{
    // pair<any, TypeEnumerator>
    switch (row.second.second) // Swith the TypeEnumerator
    {
        case 0: // int
            any_cast< function<int ()> >(row.first)();
        break;
        case 1: // float
            any_cast< function<float ()> >(row.first)();
        break;
        case 2: // double
            any_cast< function<double ()> >(row.first)();
        break;
    }
}

This won't cast and during running I get the exception:

  what():  boost::bad_any_cast: failed conversion using boost::any_cast

Is it possible to convert back the boost::function object?

+3  A: 

From the looks of it, row.first is the name of the callback, a string. You should probably use row.second.first:

case 0: // int
    any_cast< function<int ()> >(row.second.first)();
    break;

Furthermore, you should use your enum constants in the switch (case CLASS::e_int:), instead of magic numbers.

TC
>you should use your enum constants; Why?
Ockonal
@Ockonal: because it's safer. Consider what happens if you later decide you want to have an e_char constant too and accidentally add it as the first constant in your enum. Now all other enum values change, and you would have to fix the magic numbers in your switch too, or have a nasty bug.
TC
+3  A: 

@TC provided the solution for the runtime error. But I believe you should use Boost.Variant instead of Boost.Any as there are only a fixed selection of types it can store. With Boost.Variant you could eliminate that enum too, as it already provided a standard visitor pattern interface. (result):

#include <boost/variant.hpp>
#include <boost/function.hpp>
#include <boost/foreach.hpp>
#include <map>
#include <string>
#include <iostream>

typedef boost::variant<boost::function<int()>,
                       boost::function<float()>,
                       boost::function<double()> > Callback;
typedef std::map<std::string, Callback> CallbackType;

CallbackType mCallbacks;

void Foo(const std::string& name, const Callback& f) {
    mCallbacks[name] = f;
}

//------------------------------------------------------------------------------

float f() { 
    std::cout << "f called" << std::endl;
    return 4;
}

int g() {
    std::cout << "g called" << std::endl;
    return 5;
}

double h() {
    std::cout << "h called" << std::endl;
    return 4;
}

//------------------------------------------------------------------------------

struct call_visitor : public boost::static_visitor<> {
    template <typename T>
    void operator() (const T& operand) const {
        operand();
    }
};


int main () {
    Foo("f", boost::function<float()>( f ));
    Foo("g", boost::function<int()>( g ));
    Foo("h", boost::function<double()>( h ));

    BOOST_FOREACH(CallbackType::value_type &row, mCallbacks) {
        boost::apply_visitor(call_visitor(), row.second);
    }

    return 0;
}
KennyTM
+1, good point.
TC
Nice idea, thanks.
Ockonal
@KennyTM hm, how to get the returned value? `boost::apply_visitor(call_visitor(), row.second)`
Ockonal
@Ockonal: The loop body should be done in `call_visitor`. So, e.g. `void operator() (const T }`
KennyTM
@KennyTM, hm I understand this. But how to store it into need variable, eg int, float, double? Is it possible? Maybe there are any ways to get return type of the boost function and make variable using it?
Ockonal
@Ock: `... typename T::result_type result = operand(); ...`
KennyTM
@KennyTM many thanks.
Ockonal