Как работает параметр шаблона std :: function?(реализация) - PullRequest
53 голосов
/ 21 августа 2010

In Домашняя страница Бьярне Страуструпа ( C ++ 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), &X::foo преобразуется из функции-членауказатель на указатель на функцию, не являющуюся членом ?!

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

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

Ответы [ 5 ]

34 голосов
/ 21 августа 2010

После получения справки из других ответов и комментариев и чтения исходного кода GCC и стандарта C ++ 11 я обнаружил, что можно проанализировать тип функции (ее тип возврата и его типы аргументов ) с использованием частичной специализации шаблона и перегрузки функций .

Ниже приведен простой (и неполный) пример для реализации чего-то вроде std::function:

template<class T> class Function { };

// Parse the function type
template<class Res, class Obj, class... ArgTypes>
class Function<Res (Obj*, ArgTypes...)> {
    union Pointers {
        Res (*func)(Obj*, ArgTypes...);
        Res (Obj::*mem_func)(ArgTypes...);
    };

    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...);
    }

    static Res call_mem_func(Pointers& ptrs, Obj& obj, ArgTypes... args) {
        return (obj.*(ptrs.mem_func))(args...);
    }

  public:

    Function() : callback(0) { }

    // Parse the function type
    Function(Res (*func)(Obj*, ArgTypes...)) {
        ptrs.func = func;
        callback = &call_func;
    }

    // Parse the function type
    Function(Res (Obj::*mem_func)(ArgTypes...)) {
        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;
}
3 голосов
/ 21 августа 2010

Как это происходит (я полагаю) оставлено неопределенным (но у меня нет копии стандарта здесь).

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

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

Функторы:

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

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);

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

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);
2 голосов
/ 21 августа 2010

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

1 голос
/ 12 ноября 2018

Ответить на вопрос в названии. Параметр, который std::function использует, является хорошим трюком для передачи многих параметров в качестве одного параметра шаблона. Эти аргументы являются типами аргументов и типом возвращаемой функции.

Это совпадает с тем, что std::function пытается стереть тип общего функтора, но это просто совпадение.

На самом деле, когда-то были компиляторы, которые не принимали такие трюки, а прекурсор boost::function имел синтаксис portable , с помощью которого все параметры можно было передавать отдельно: 1010 *

Предпочитаемый синтаксис

boost::function<void(int*, int, int&, float&)> sum_avg;

Переносимый синтаксис

boost::function4<void, int*, int, int&, float&> sum_avg;

https://www.boost.org/doc/libs/1_68_0/doc/html/function/tutorial.html#id-1.3.16.5.4

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

1 голос
/ 21 августа 2010

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

...