web-dev-qa-db-ja.com

std :: functionのテンプレートパラメータはどのように機能しますか? (実装)

Bjarne Stroustrupのホームページ( 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 :: functionfooメンバー関数をどのように呼び出しますか?

テンプレートパラメーターはint(X*, int)であり、_&X::foo_はメンバー関数ポインターから非メンバー関数ポインター?!

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

明確にするために:std :: functionを使用するためにポインターをキャストする必要がないことはわかっていますが、 std :: functionは、メンバー関数ポインター非メンバー関数ポインター。標準でどのようにstd :: functionのようなものを実装できるかわかりません!

54
M. Sadeq H. E.

他の回答やコメントから助けを得て、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;
}
34
M. Sadeq H. E.

それがどのように行われるか(私は信じています)は未定義のままです(ただし、ここには標準のコピーがありません)。

しかし、カバーする必要のあるさまざまな可能性をすべて考慮すると、それがどのように機能するかの正確な定義を解読するのは本当に難しいだろうと感じています。

しかし、ファンクタがどのように機能するかを知りたいと思います。ファンクタは比較的単純です。これが簡単な例です。

ファンクタ:

これらは、関数のように機能するオブジェクトです。
テンプレートコードでは、オブジェクトや関数を交換して使用できることが多いため、非常に便利です。ファンクタの優れた点は、状態を保持できることです(一種の貧乏人の閉鎖)。

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

またはさらに進んで、パラメータをバインドします。したがって、提供する必要があるのはパラメータの1つだけです。

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
Martin York

g ++には、関数ポインター、メンバーポインター、またはおそらくファンクターを指すvoidポインターのいずれかを保持できる共用体があるようです。有効なユニオンメンバーに適切にフラグを付けるオーバーロードを追加し、スープに大量にキャストすると、機能します...

2
Tomek

関数ポインタではありません。それがstd :: functionが存在する理由です。それはあなたがそれを与えるどんな呼び出し可能タイプでもラップします。 boost :: bindをチェックする必要があります。これは、メンバー関数ポインターを(this、args)として呼び出し可能にするためによく使用されます。

1
Puppy

タイトルの質問に答える。 std::functionが使用するパラメーターは、多くのパラメーターを単一のテンプレートパラメーターとして渡すための優れたトリックです。これらの引数は、関数の引数型と戻り型です。

std::functionが一般的なファンクターをタイプ消去しようとするのは偶然ですが、それは単なる偶然です。

実際、昔々、そのようなトリックを受け入れないコンパイラがあり、boost::function前駆体にはportable構文がありましたこれにより、すべてのパラメーターを個別に渡すことができます。

推奨される構文

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
alfC