Как работает параметр шаблона std:: function? (реализация)

В домашней странице Bjarne Stroustrup (С++ 11 FAQ):

struct X { int foo(int); };

std::function<int(X*, int)> f;
f = &X::foo; //pointer to member

X x;
int v = f(&x, 5); //call X::foo() for x with 5

Как это работает? Как std:: function вызывает функцию-член foo?

Параметр шаблона int(X*, int), is &X::foo преобразован из указателя функции-члена в указатель функции нечлена?!

(int(*)(X*, int))&X::foo //casting (int(X::*)(int) to (int(*)(X*, int))

Чтобы уточнить: я знаю, что нам не нужно указывать какой-либо указатель на использование функции std::, но я не знаю, как внутренние функции std:: function обрабатывают эту несовместимость между указателем на функцию-член и -member function pointer. Я не знаю, как стандарт позволяет нам реализовать что-то вроде std:: function!

Ответ 1

Спасибо за все ответы.

Я нашел хороший пример из раздела 14.8.2.5-21 стандарта:

template<class> struct X { };
template<class R, class ... ArgTypes> struct X<R(int, ArgTypes ...)> { };
template<class ... Types> struct Y { };
template<class T, class ... Types> struct Y<T, Types& ...> { };
template<class ... Types> int f(void (*)(Types ...));
void g(int, float);
// uses primary template
X<int> x1;
// uses partial specialization; ArgTypes contains float, double
X<int(int, float, double)> x2;
// uses primary template
X<int(float, int)> x3;
// use primary template; Types is empty
Y<> y1;
// uses partial specialization; T is int&, Types contains float, double
Y<int&, float&, double&> y2;
// uses primary template; Types contains int, float, double
Y<int, float, double> y3;
// OK; Types contains int, float
int fv = f(g);

В нем говорится, что с помощью специализации шаблона мы можем анализировать параметры шаблона типа функции (удивительные)! Ниже приведен грязный/простой пример о том, как работает std:: function:

template<class T> struct Function { };

template<class T, class Obj, class... Args>
struct Function<T(Obj*, Args...)> // Parsing the function type
{
    enum FuncType
    {
        FuncTypeFunc,
        FuncTypeMemFunc
    };
    union FuncPtr
    {
        T(*func)(Obj*, Args...);
        T(Obj::*mem_func)(Args...);
    };

    FuncType m_flag;
    FuncPtr m_func_ptr;

    Function(T(*func)(Obj*, Args...)) // void(*)(Funny*, int, int)
    {
        m_flag = FuncTypeFunc;
        m_func_ptr.func = func;
    }
    Function(T(Obj::*mem_func)(Args...)) // void(Funny::*)(int, int)
    {
        m_flag = FuncTypeMemFunc;
        m_func_ptr.mem_func = mem_func;
    }

    void play(Obj* obj, Args... args)
    {
        switch(m_flag)
        {
          case FuncTypeFunc:
            (*m_func_ptr.func)(obj, args...);
            break;
          case FuncTypeMemFunc:
            (obj->*m_func_ptr.mem_func)(args...);
            break;
        }
    }
};

Использование:

#include <iostream>

struct Funny
{
    void done(int i, int j)
    {
        std::cout << "Member Function: " << i << ", " << j << std::endl;
    }
};

void done(Funny* funny, int i, int j)
{
    std::cout << "Function: " << i << ", " << j << std::endl;
}

int main(int argc, char** argv)
{
    Funny funny;
    Function<void(Funny*, int, int)> f = &Funny::done; // void(Funny::*)(int, int)
    Function<void(Funny*, int, int)> g = &done; // void(*)(Funny*, int, int)
    f.play(&funny, 5, 10); // void(Funny::*)(int, int)
    g.play(&funny, 5, 10); // void(*)(Funny*, int, int)
    return 0;
}

Редактировать: Благодаря Томеку за его хороший намек на union s, приведенный выше пример изменился, чтобы удерживать указатель на функцию члена/нечлена в одной (не две) переменной.


Изменить: Мартин Йорк прав, оператор switch не был хорошей идеей в приведенном выше примере, поэтому я полностью изменил пример, чтобы работать лучше:

template<class T> class Function { };

template<class Res, class Obj, class... ArgTypes>
class Function<Res (Obj*, ArgTypes...)> // Parsing the function type
{
    union Pointers // An union to hold different kind of pointers
    {
        Res (*func)(Obj*, ArgTypes...); // void (*)(Funny*, int)
        Res (Obj::*mem_func)(ArgTypes...); // void (Funny::*)(int)
    };
    typedef Res Callback(Pointers&, Obj&, ArgTypes...);

    Pointers ptrs;
    Callback* callback;

    static Res call_func(Pointers& ptrs, Obj& obj, ArgTypes... args)
    {
        return (*ptrs.func)(&obj, args...); // void (*)(Funny*, int)
    }
    static Res call_mem_func(Pointers& ptrs, Obj& obj, ArgTypes... args)
    {
        return (obj.*(ptrs.mem_func))(args...); // void (Funny::*)(int)
    }

  public:

    Function() : callback(0) { }
    Function(Res (*func)(Obj*, ArgTypes...)) // void (*)(Funny*, int)
    {
        ptrs.func = func;
        callback = &call_func;
    }
    Function(Res (Obj::*mem_func)(ArgTypes...)) // void (Funny::*)(int)
    {
        ptrs.mem_func = mem_func;
        callback = &call_mem_func;
    }
    Function(const Function& function)
    {
        ptrs = function.ptrs;
        callback = function.callback;
    }
    Function& operator=(const Function& function)
    {
        ptrs = function.ptrs;
        callback = function.callback;
        return *this;
    }
    Res operator()(Obj& obj, ArgTypes... args)
    {
        if(callback == 0) throw 0; // throw an exception
        return (*callback)(ptrs, obj, args...);
    }
};

Использование:

#include <iostream>

struct Funny
{
    void print(int i)
    {
        std::cout << "void (Funny::*)(int): " << i << std::endl;
    }
};

void print(Funny* funny, int i)
{
    std::cout << "void (*)(Funny*, int): " << i << std::endl;
}

int main(int argc, char** argv)
{
    Funny funny;
    Function<void(Funny*, int)> wmw;

    wmw = &Funny::print; // void (Funny::*)(int)
    wmw(funny, 10); // void (Funny::*)(int)

    wmw = &print; // void (*)(Funny*, int)
    wmw(funny, 8); // void (*)(Funny*, int)

    return 0;
}

Ответ 2

Как это делается (я считаю) осталось undefined (но у меня нет копии стандарта здесь).

Но учитывая все различные возможности, которые необходимо охватить, я чувствую, что дешифрование точного определения того, как это работает, было бы очень сложно: так что я не собираюсь пытаться.

Но я думаю, вы хотели бы знать, как работают функторы, и они относительно просты. Итак, вот краткий пример.

функторы:

Это объекты, которые действуют как функции.
Они очень полезны в коде шаблонов, поскольку они часто позволяют использовать объекты или функции взаимозаменяемо. Самое замечательное в функторах заключается в том, что они могут удерживать состояние (своего рода плохое закрытие человека).

struct X
{
     int operator()(int x) { return doStuff(x+1);}
     int doStuff(int x)    { return x+1;}
};

X   x;  // You can now use x like a function
int  a = x(5);

Вы можете использовать тот факт, что functor hold state содержит объекты, такие как параметры или объекты, или указатель на методы-члены (или любую их комбинацию).

struct Y // Hold a member function pointer
{
    int (X::*member)(int x);
    int operator(X* obj, int param) { return (obj->*member)(param);}
};
X  x;
Y  y;
y.member = &X::doStuff;
int a = y(&x,5);

Или идите дальше и привяжите параметры. Итак, теперь все, что вам нужно предоставить, является одним из параметров.

struct Z
{
    int (X::*member)(int x);
    int  param;
    Z(int (X::*m)(int), int p) : member(m), param(p) {}

    int operator()(X* obj)  { return (obj->*member)(param);}
    int operator()(X& obj)  { return (obj.*member)(param);}
};

Z z(&X::doStuff,5);

X x;
int a = z(x);

Ответ 3

g++, похоже, имеет объединение, которое может содержать либо указатель на функцию, либо указатель-член, либо указатель void, который, вероятно, указывает на функтор. Добавьте перегрузки, которые надлежащим образом отмечают, какой член профсоюза действителен и тяжело бросается в суп, а затем он работает...

Ответ 4

Они не являются указателями на функции. Это то, для чего существует функция std::. Он обматывает любые вызываемые типы, которые вы им даете. Вы должны проверить boost:: bind - он часто используется для указания указателей на функции функций (это, args).