3

So imagine we had 2 functions (void : ( void ) ) and (std::string : (int, std::string)) and we could have 10 more. All (or some of them) take in different argument types and can return different types. We want to store them in a std::map, so we get an API like this:

//Having a functions like:
int hello_world(std::string name, const int & number )
{
    name += "!";
    std::cout << "Hello, " << name << std::endl;
    return number;
}
//and
void i_do_shadowed_stuff()
{
    return;
}

//We want to be capable to create a map (or some type with similar API) that would hold our functional objects. like so:
myMap.insert(std::pair<std::string, fun_object>("my_method_hello", hello_world) )
myMap.insert(std::pair<std::string, fun_object>("my_void_method", i_do_shadowed_stuff) )
//And we could call tham with params if needed:
int a = myMap["my_method_hello"]("Tim", 25);
myMap["my_void_method"];

I wonder how to put many different functions into the same container. Specifically, how to do this in C++03 using Boost.

The API should be independent from the actual function types (having int a = myMap["my_method_hello"]("Tim", 25); not int a = myMap<int, (std::string, int)>["my_method_hello"]("Tim", 25);).

6
  • 3
    How would you know which elements to call with the first prototype, and which with the second? Commented Nov 29, 2011 at 1:10
  • We assume programmer knows what API he created. But how to create such thing in the first place?
    – myWallJSON
    Commented Nov 29, 2011 at 1:14
  • 4
    Then why not use a struct of function(-object) pointers? With a map, you're asking for compile-time support for knowledge that you only have at run-time. It doesn't make sense in C++. Commented Nov 29, 2011 at 1:16
  • 1
    I just read about this in stackoverflow: stackoverflow.com/a/3176186/1058916 , which may interest you.
    – fefe
    Commented Nov 29, 2011 at 1:22
  • @fefe: That makes my answer a little clearer. A little. Thanks! Commented Nov 29, 2011 at 1:32

5 Answers 5

6
#include <functional>
#include <iostream>
#include <string>
#include <map>

class api {
    // maps containing the different function pointers
    typedef void(*voidfuncptr)();
    typedef int(*stringcrintptr)(std::string, const int&);

    std::map<std::string, voidfuncptr> voida;
    std::map<std::string, stringcrintptr> stringcrint;
public:
    // api temp class
    // given an api and a name, it converts to a function pointer
    // depending on parameters used
    class apitemp {
        const std::string n;
        const api* p;
    public:
        apitemp(const std::string& name, const api* parent)
            : n(name), p(parent) {}
        operator voidfuncptr()
        { return p->voida.find(n)->second; }
        operator stringcrintptr()
        { return p->stringcrint.find(n)->second; }
    };

    // insertion of new functions into appropriate maps
    void insert(const std::string& name, voidfuncptr ptr)
    { voida[name]=ptr; }
    void insert(const std::string& name, stringcrintptr ptr)
    { stringcrint[name]=ptr; }
    // operator[] for the name gets halfway to the right function
    apitemp operator[](std::string n) const
    { return apitemp(n, this); }
};

Usage:

api myMap; 

int hello_world(std::string name, const int & number )
{
    name += "!";
    std::cout << "Hello, " << name << std::endl;
    return number;
}

int main()
{
    myMap.insert("my_method_hello", &hello_world );
    int a = myMap["my_method_hello"]("Tim", 25);
}

Not very pretty. Better advice is to not do anything even remotely like whatever it is you're trying to do.

Note that this requires all functions with the same parameters to return the same type.

9
  • 1
    Yes indeed. I'm not even going to attempt to figure out what this is doing. Commented Nov 29, 2011 at 1:27
  • @OliCharlesworth: commented. But yes. It's a monstrosity even when you know what it's doing. Commented Nov 29, 2011 at 1:31
  • problem is it is not... so to say live... I mean you can live with such thing when its class hidden deep inside API... But this implementation shall be changed each time new function type is introduces so you cant just say class.push<any_return_T, array_of_input_T's>(function_name)
    – myWallJSON
    Commented Dec 1, 2011 at 19:24
  • Correct. This will only work for function types that have been specified beforehand. For those specified, you can simply insert them though. Commented Dec 1, 2011 at 19:34
  • You can work around the return value thing I think with std::function, but I'm not sure how. You will still have to provide every possible combination of parameter types though. Commented Dec 1, 2011 at 19:36
4

You can use boost::any...

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

void voidFunc()
{
    std::cout << "void called" << std::endl;
}

void stringFunc(std::string str)
{
    std::cout << str << std::endl;
}

int main()
{
    std::map<std::string, boost::any> funcs;
    funcs.insert(std::pair<std::string, boost::any>("voidFunc", &voidFunc));
    funcs.insert(std::pair<std::string, boost::any>("stringFunc", &stringFunc));

    boost::any_cast<void(*)(void)>(funcs["voidFunc"])();
    boost::any_cast<void(*)(std::string)>(funcs["stringFunc"])("hello");
    return 0;
}

Note that you will get a runtime exception if you don't specify the function signature correctly in the any_cast.

3
  • can we any how get automated (on compile time) type validation with/via Boost.Variant somehow adding new types to it when inserting into map?
    – myWallJSON
    Commented Nov 29, 2011 at 4:08
  • @myWallJSON: Not sure if I understand you correctly, but probably yes. If you replace boost::any with boost::variant<T1, T2, T3> then you can only insert those three types into the map.
    – MSalters
    Commented Nov 29, 2011 at 8:02
  • @MSalters: see dedicated Q
    – myWallJSON
    Commented Nov 29, 2011 at 9:03
4

Complete example here and here.

We can use type erasure like boost::any does. But since we want to hold only functions, we can come up with more convenient usage.

The first thing we need is function signature deduction. See here.

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template<typename R, typename ...Args> 
struct function_traits<std::function<R(Args...)>>
{
    typedef R result_type;
    typedef typename std::function<R(Args...)> type;
    typedef typename std::function<void(Args...)> typeNoRet;
};

template<typename R, typename ...Args> 
struct function_traits<R(*)(Args...)>
{
    typedef R result_type;
    typedef typename std::function<R(Args...)> type;
    typedef typename std::function<void(Args...)> typeNoRet;
};

template<typename R, typename cls, typename ...Args> 
struct function_traits<R(cls::*)(Args...)>
{
    typedef R result_type;
    typedef typename std::function<R(Args...)> type;
    typedef typename std::function<void(Args...)> typeNoRet;
};

To implement type erasure we need base class which is not template, and derived template class. FunctionHolder will store base class pointer. Constructor will use function_traits to determine correct derived class type.

class FunctionHolder {
    private:
        struct BaseHolder {
            BaseHolder() {}
            virtual ~BaseHolder() {}
        };

        template <typename T>
        struct Holder : public BaseHolder {
            Holder(T arg) : mFptr(arg) {}

            template<typename... Args>
            void Call(Args&&...args) {
                mFptr(std::forward<Args>(args)...);
            }

            template<typename R, typename... Args>
            R CallRet(Args&&...args) {
                return mFptr(std::forward<Args>(args)...);
            }

            T mFptr;
        };

    public:
        template<typename T>
        FunctionHolder(T t) : mBaseHolder(new Holder<typename function_traits<T>::type>(t))
                            , mBaseHolderNoRet(new Holder<typename function_traits<T>::typeNoRet>(t)) {}

        template<typename T, typename...Args>
        FunctionHolder(T&& t, Args&&... args) : mBaseHolder(new Holder<typename function_traits<T>::type>
                                                    (std::bind(std::forward<T>(t), std::forward<Args>(args)...)))
                                              , mBaseHolderNoRet(new Holder<typename function_traits<T>::typeNoRet>
                                                    (std::bind(std::forward<T>(t), std::forward<Args>(args)...))) {}

    void operator()() {
        this->operator()<>();
    }

    template<typename... Args>
    void operator()(Args&&... args) {
        auto f = dynamic_cast<Holder<std::function < void(Args...) > >*>(mBaseHolderNoRet.get());
        if (f) {
            f->Call(std::forward<Args>(args)...);
            return;
        }
        throw std::invalid_argument("");
    }

    template<typename R, typename... Args>
    R call(Args&&... args) {
        auto f = dynamic_cast<Holder<std::function<R(Args...)>>*>(mBaseHolder.get());
        if (f) {
            return f->template CallRet<R>(std::forward<Args>(args)...);
        }
        throw std::invalid_argument("");
    }

    private:
        std::unique_ptr<BaseHolder> mBaseHolder;
        std::unique_ptr<BaseHolder> mBaseHolderNoRet;
};

In this case FunctionHolder stores 2 pointers of the BaseHolder, the first one with the correct signature and second one returning void. To avoid overheads you may remove one of them, if your are going to always specify return value or never use it.

And finally we may use it like this.

struct st0 {
    st0(int x) : mX(x) {}

    std::string  print(int p) {
        std::cout << "st0::print " 
                  << mX << " " << p << std::endl;
        return "ret_from_st0::print";
    }
    int mX;
};

struct st1 {
    st1(int x) : mX(x) {}

    void operator()() {
        std::cout << "st1::operator() " 
                  << mX << " " << std::endl;
    }
    int mX;
};

void Func0(int a, int b) {
    std::cout << "Func0. "
              << " a: " << a
              << " b: " << b << std::endl;
}

void Func1(int a, int b, std::string str) {
    std::cout << "Func0. "
              << " a: " << a
              << " b: " << b
              << " str: " << str << std::endl;
}

uint64_t Func2(int a, int b, std::string str) {
    std::cout << "Func0. "
              << " a: " << a
              << " b: " << b
              << " str: " << str << std::endl;
    return 0xBAB0CAFE;
}

int main() {
    try {
        // void(int, int)
        FunctionHolder ex1(&Func0);
        ex1(1,12);
    
        // void(int, int, std::string)
        FunctionHolder ex2(&Func1);
        ex2(1, 12, std::string("Some text here"));
        
        // int(int, int, std::string)
        // call and print return value
        FunctionHolder ex3(&Func2);
        std::cout << "Ret: " << std::hex << ex3.call<uint64_t>(123, 3211, std::string("another text")) 
                  << std::dec << std::endl;
        // call and drop return value
        ex3(123, 3211, std::string("another text"));
    
        // Hold std::function<void(int)>
        std::function<void(int)> ex4 = std::bind(&Func0, 1, std::placeholders::_1);
        FunctionHolder c(std::function<void(int)>(std::bind(&Func0, 1, std::placeholders::_1)));
        ex4(12);
    
        // will bind to st0 member function print
        st0 st0object(8955);
        FunctionHolder ex5(&st0::print, st0object, std::placeholders::_1);
        ex5(2222);
        // call and print return value
        std::cout << "Ret: " << ex5.call<std::string>(7531) << std::endl;
    
        // wrap lambda function with std::function and pass to holder
        FunctionHolder ex6(std::function<void(void)>([]() {std::cout << "lambda function called" << std::endl;}));
        ex6();
    
        // functor object st1
        FunctionHolder ex7(st1(123654));
        ex7();
        
        // Will throw, because st1::operator() gets no arguments
        ex7(123);
    } catch (std::invalid_argument &e) {
        std::cout << "Invalid argument(s) were passed" << std::endl;
        // error handling here...
    }

    return 0;
}

FunctionHolder may store C like function pointers, functor objects, lambda functions and member function pointers. There are only 2 exceptions to remember.

  1. If you are going to use std::bind to bind object pointer to member function, you may pass all arguments to the FunctionHolder constructor which will do binding for you. If you want to bind any other function argument(s) you have to bind it manually, construct std::function and pass it to the FunctionHolder.
FunctionHolder fh(std::function<void(std::string)>(std::bind(&Func1, 888, 333, std::placeholders::_1)));
fh(std::string("Ok."));
  1. To store lambda functions, first wrap them with the std::function, as in example above.

In case of passing wrong arguments, FunctionHolder will throw std::invalid_argument.

2

The thing is, somehow, when you call your functions, you already know what type they will be.

If we do something like

int x = map["key"](1, "2")

we can already deduce that whatever function is stored in "key" is of type (int (*)(int, char*)) so we might as well have done something like

int x = map_of_int_and_string_to_int["key"](1, "2");

and avoid all the hassle of merging all the keys together... While it is true that C++ has some overloading features precisely for this kind of stuff I can't really see why you should bother in this particular case.

And in the end, why would you want to put all those functions in the same map in the first place? They don't share any similar interfaces so you can't uniformly access them, you can't iterate over them and you can't opaquely pass them around to someone else. Without anything in common there is nothing you can safely do to the functions in this hypothetical map.

2
  • I try to implement a task pool over MPI. So I need some kind of RPC but one that would work between difrent parts of my programm, meaning processor A wants processor B to call function C with argument D. We can not pass pointers to functions between processes like we do with threads, so we need some wrapper container to hold our function pointers at each process instance. All inside one source file\one programm...
    – myWallJSON
    Commented Dec 1, 2011 at 19:27
  • @myWallJSON: How are you going to pass the abstract parameters? Commented Dec 4, 2011 at 0:03
-1

You can do it by casting function pointers to void pointers and back. You're expected to know the signature during run-time, so it wouldn't be an issue to hard-wire the casting operators. However the logic of doing it escapes me. It doesn't make sense at all, at least in C++. Using template classes/functions or structs of function pointers makes much more sense.

for example, with templates:

template <typename X> foo(X param1) { /* do something with param1*/};
template <typename X, typename Y> foo(X param1, Y param2)
   {/* do something with 2 params*/};
template <int X> foo(X param1) { /* only one parameter, which is int */};

now:

foo(5); // calls the third one
foo("5"); // calls the first one
foo("5", 5); // calls the second one.

Who needs a map?

3
  • Why are templates needed here? Commented Nov 29, 2011 at 1:28
  • @Oli - just an example, don't have to use templates, you can achieve it in various different ways. In my particular example I was trying to show a generic function that accepts a parameter of any type (through template), and a specific function that accepts int, but it may appear a bit forced.
    – littleadv
    Commented Nov 29, 2011 at 1:30
  • 5
    All you´re doing here is overloading. How do you put these functions into a container, together? That is the question. Commented Nov 29, 2011 at 1:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.