Search code examples
c++11function-pointersboost-bindboost-variant

Storing function pointers with different types c++ boost::bind


I have dug around quite a bit today and have come up empty. Is there any way to store a functor that is returned from a boost::bind with different types? I found an example that used boost::variants but not sure that this is needed. (Foo and Bar have been simplified for simplicity sake)

#include <boost/bind.hpp>
#include <boost/variant.hpp>
#include <boost/function.hpp>

#include <map>
#include <iostream>

template <typename FooType>
struct Foo {
    const FooType tmp_value;

    Foo(const FooType& tmp_) :
    tmp_value(tmp_)
    {
    }

    template<typename Object>
    void operator()(Object& operand)
    {
        std::cout << operand << std::endl;
        operand += tmp_value;
    }
};

template <typename BarType>
struct Bar {
    const BarType tmp_value;

    Bar(const BarType& tmp_) :
    tmp_value(tmp_)
    {
    }

    template<typename Object>
    void operator()(Object& operand)
    {
        std::cout << operand << std::endl;
        operand -= tmp_value;
    }
};

typedef boost::variant<
    boost::function<void(int32_t)>,
    boost::function<void(int64_t)>,
    boost::function<void(double)>,
    boost::function<void(float)>
> my_functions;

typedef std::map<std::string, my_functions> test_map;

enum test_t {
    FOO,
    BAR
};

test_map createFunMap() {
  test_map result;

    for(int i = 0; i < 2; i++) {
        switch(i) {
            case test_t::FOO: {
                std::cout << "In FOO" << std::endl;
                Foo<double> p(1.0);
                result.insert(std::pair<std::string, 
                                        boost::function<void(double)>>
                                        ("foo", boost::bind<void>(p, _1)));
              break;
            }
            case test_t::BAR: {
                std::cout << "In BAR" << std::endl;
                Bar<int32_t> p(1.0);
                result.insert(std::pair<std::string, 
                                        boost::function<void(int32_t)>>
                                        ("bar", boost::bind<void>(p, _1)));
              break;
            }
            default:
              std::cout << "just a default" << std::endl;
              break;
        }
    }

  return result;
}

int main() {
    test_map myMap;
    double t = 5.0;

    myMap = createFunMap();

    std::cout << t << std::endl;
    myMap["foo"](t);
    std::cout << t << std::endl;

    return 0;
}

compiler output:

g++ -Wall --std=c++0x -I. test_ptrs.cc -o test_ptrs
test_ptrs.cc:93:2: error: type 'mapped_type' (aka 'boost::variant<boost::function<void (int)>,         boost::function<void (long long)>, boost::function<void (double)>, boost::function<void (float)>,
  boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_,
  boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_,
  boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_, boost::detail::variant::void_>') does not provide a call operator
    myMap["foo"](t);
    ^~~~~~~~~~~~
1 error generated.

Thanks.


Solution

  • You have polymorphic functors (Foo and Bar).

    You want to type erase them for a certain set of operand types. I suggest defining a type-erased functor type for the purpose:

    struct ErasedFunctor
    {
        template<typename F> ErasedFunctor(F&& f) 
            : pimpl(new impl_<F>(std::forward<F>(f))) {}
    
        template <typename T>
        void operator()(T& oper) const {
            assert(pimpl);
            pimpl->call(oper);
        }
    
      private:
        typedef boost::variant<int32_t&, int64_t&, double&, float&> Operand;
    
        struct base_ { virtual void call(Operand oper) const = 0; };
        // struct impl_ : base_ ... 
    
        std::shared_ptr<base_> pimpl;
    };
    

    Now you can simply store the function objects directly in the map:

    typedef std::map<std::string, ErasedFunctor> test_map;
    
    test_map createFunMap() {
        return test_map { 
            { "foo", Foo<double>(1.0) },
            { "bar", Bar<int32_t>(1)  },
        };
    }
    

    Let's use at("foo") instead of ["foo"] to avoid having to make ErasedFunctor default-constructible:

    int main() {
        test_map myMap = createFunMap();
        double t = 5.0;
    
        std::cout << t << std::endl;
        myMap.at("foo")(t);
        std::cout << t << std::endl;
        myMap.at("bar")(t);
        std::cout << t << std::endl;
    }
    

    Prints

    5
    void ErasedFunctor::apply::operator()(const F&, T&) const [with F = Foo<double>; T = double](5)
    5
    6
    void ErasedFunctor::apply::operator()(const F&, T&) const [with F = Bar<int>; T = double](6)
    6
    5
    

    See it Live On Coliru

    For more background see:


    Full Sample

    #include <boost/bind.hpp>
    #include <boost/variant.hpp>
    #include <iostream>
    
    template <typename FooType> struct Foo {
        const FooType tmp_value;
    
        Foo(const FooType &tmp_) : tmp_value(tmp_) {}
    
        template <typename Object> void operator()(Object &operand) const {
            std::cout << operand << std::endl;
            operand += tmp_value;
        }
    };
    
    template <typename BarType> struct Bar {
        const BarType tmp_value;
    
        Bar(const BarType &tmp_) : tmp_value(tmp_) {}
    
        template <typename Object> void operator()(Object &operand) const {
            std::cout << operand << std::endl;
            operand -= tmp_value;
        }
    };
    
    struct ErasedFunctor
    {
        template<typename F> ErasedFunctor(F&& f) 
            : pimpl(new impl_<F>(std::forward<F>(f))) {}
    
        template <typename T>
        void operator()(T& oper) const {
            assert(pimpl);
            pimpl->call(oper);
        }
    
      private:
        typedef boost::variant<int32_t&, int64_t&, double&, float&> Operand;
    
        struct base_ { virtual void call(Operand oper) const = 0; };
    
        struct apply : boost::static_visitor<void> {
            template <typename F, typename T> void operator()(F const& f, T& v) const {
                std::cout << __PRETTY_FUNCTION__ << "(" << v << ")\n";
                f(v);
            }
        };
    
        template <typename F> struct impl_ : base_ {
            F f_;
            impl_(F&& f) : f_(std::forward<F>(f)) { }
            virtual void call(Operand oper) const override {
                boost::apply_visitor(boost::bind(apply(), boost::cref(f_), _1), oper);
            }
        };
    
        std::shared_ptr<base_> pimpl;
    };
    
    #include <map>
    typedef std::map<std::string, ErasedFunctor> test_map;
    
    test_map createFunMap() {
        return test_map { 
            { "foo", Foo<double>(1.0) },
            { "bar", Bar<int32_t>(1)  },
        };
    }
    
    int main() {
        test_map myMap = createFunMap();
        double t = 5.0;
    
        std::cout << t << std::endl;
        myMap.at("foo")(t);
        std::cout << t << std::endl;
        myMap.at("bar")(t);
        std::cout << t << std::endl;
    }