7

コールバック メカニズムの登録を実行するためのクリーンなパブリック インターフェイスをクラスに持たせる方法を見つけようとして、数日間頭を悩ませていました。コールバックは、C++11 ラムダ、、、、std::function<void(Type1,Type2)>またはの結果にすることができます。std::function<void(Type2)>std::function<void()>std::bind()

このインターフェイスの重要な点は、クラスのユーザーが、ユーザーがスローする可能性のあるほぼすべてのファンクター/コールバック メカニズムを受け入れる 1 つのパブリック インターフェイスについて知っているだけでよいということです。

ファンクターとインターフェースの登録を示す簡略化されたクラス

struct Type1;
struct Type2; // May be the same type as Type1
class MyRegistrationClass
{
public:
    /**
     * Clean and easy to understand public interface:
     * Handle registration of any functor matching _any_ of the following
     *    std::function<void(Type1,Type2)>
     *    std::function<void(Type2)>        <-- move argument 2 into arg 1
     *    std::function<void()>
     *    or any result of std::bind() requiring two or fewer arguments that
     *    can convert to the above std::function< ... > types.
     */
    template<typename F>
    void Register(F f) {
       doRegister(f);
    }
private:
    std::list< std::function< void(Type1, Type2) > > callbacks;


    // Handle registration for std::function<void(Type1,Type2)>
    template <typename Functor>
    void doRegister(const Functor & functor,
                           typename std::enable_if< 
                                   !is_bind_expr<Functor>
                                   && functor_traits<decltype(&Functor::operator())>::arity == 2
                               >::type * = nullptr )
    {
        callbacks.push_back( functor );
    }

    // Handle registration for std::function<void(Type2)> by using std::bind
    // to discard argument 2 ...
    template <typename Functor>
    void doRegister(const Functor & functor, 
                           typename std::enable_if< 
                                   !std::is_bind_expression< Functor >::value
                                   && functor_traits<decltype(&Functor::operator())>::arity == 1
                               >::type * = nullptr )
    {
        // bind _2 into functor
        callbacks.push_back( std::bind( functor,                              std::placeholders::_2 ) );
    }

    // Handle registration for std::function<void(Type2)> if given the results
    // of std::bind()
    template <typename Functor>
    void doRegister(const Functor & functor,
                           typename std::enable_if< 
                                   is_bind_expr<Functor>
///////////////////////////////////////////////////////////////////////////
//// BEGIN Need arity of a bounded argument
///////////////////////////////////////////////////////////////////////////
                                   && functor_traits<decltype(Functor)>::arity == 1  
///////////////////////////////////////////////////////////////////////////
//// END need arity of a bounded argument
///////////////////////////////////////////////////////////////////////////
                               >::type * = nullptr )
    {
        // Push the result of a bind() that takes a signature of void(Type2)
        // and push it into the callback list, it will automatically discard
        // argument1 when called, since we didn't bind _1 placeholder
        callbacks.push_back( functor );
    }

    // And other "doRegister" methods exist in this class to handle the other
    // types I want to support ...
}; // end class

enable_if<> の使用が複雑になる唯一の理由は、特定のメソッドをオン/オフすることです。これを行う必要があるのは、 std::bind() の結果を Register() メソッドに渡したい場合で、次のような単純な署名がある場合、複数の登録メソッドに対してあいまいに一致する可能性があるためです。

void doRegister( std::function< void(Type1, Type2) > arg );
void doRegister( std::function< void(Type2) > arg ); // NOTE: type2 is first arg
void doRegister( std::function< void() > arg );

車輪を再発明するのではなく、traits.hppを参照してから、std::bind() のサポートを追加する「functor_traits」という名前の独自の特性ヘルパーでラップしました。

これは、バインドされた関数「アリティ」を識別するためにこれまでに思いついたものです...またはバインド結果が次のように期待する引数の数:

バインド結果のアリティを見つけようとする私の試み

#include <stdio.h>
// Get traits.hpp here: https://github.com/kennytm/utils/blob/master/traits.hpp
#include "traits.hpp" 

using namespace utils;
using namespace std;

void f1() {};
int f2(int) { return 0; }
char f3(int,int) { return 0; }

struct obj_func0 
{
    void operator()() {};
};
struct obj_func1
{
    int operator()(int) { return 0; };
};
struct obj_func2 
{
    char operator()(int,int) { return 0; };
};


/**
 * Count the number of bind placeholders in a variadic list
 */
template <typename ...Args>
struct get_placeholder_count
{
    static const int value = 0;
};
template <typename T, typename ...Args >
struct get_placeholder_count<T, Args...>
{
    static const int value = get_placeholder_count< Args... >::value + !!std::is_placeholder<T>::value;
};


/**
 * get_bind_arity<T> provides the number of arguments 
 * that a bounded expression expects to have passed in. 
 *  
 * This value is get_bind_arity<T>::arity
 */

template<typename T, typename ...Args>
struct get_bind_traits;

template<typename T, typename ...Args>
struct get_bind_traits< T(Args...) >
{
    static const int arity = get_placeholder_count<Args...>::value;
    static const int total_args = sizeof...(Args);
    static const int bounded_args = (total_args - arity);
};

template<template<typename, typename ...> class X, typename T, typename ...Args>
struct get_bind_traits<X<T, Args...>>
{
    // how many arguments were left unbounded by bind
    static const int arity        = get_bind_traits< T, Args... >::arity;

    // total arguments on function being called by bind
    static const int total_args   = get_bind_traits< T, Args... >::total_args;

    // how many arguments are bounded by bind:
    static const int bounded_args = (total_args - arity);

    // todo: add other traits (return type, args as tuple, etc
};

/**
 * Define wrapper "functor_traits" that wraps around existing function_traits
 */
template <typename T, typename Enable = void >
struct functor_traits;

// Use existing function_traits library (traits.hpp)
template <typename T>
struct functor_traits<T, typename enable_if< !is_bind_expression< T >::value >::type > :
    public function_traits<T>
{};

template <typename T>
struct functor_traits<T, typename enable_if< is_bind_expression< T >::value >::type >
{
    static const int arity = get_bind_traits<T>::arity;
};

/**
 * Proof of concept and test routine
 */
int main()
{
    auto lambda0 = [] {};
    auto lambda1 = [](int) -> int { return 0; };
    auto lambda2 = [](int,int) -> char { return 0;};
    auto func0 = std::function<void()>();
    auto func1 = std::function<int(int)>();
    auto func2 = std::function<char(int,int)>();
    auto oper0 = obj_func0();
    auto oper1 = obj_func1();
    auto oper2 = obj_func2();
    auto bind0 = bind(&f1);
    auto bind1 = bind(&f2, placeholders::_1);
    auto bind2 = bind(&f1, placeholders::_1, placeholders::_2);
    auto bindpartial = bind(&f1, placeholders::_1, 1);

    printf("action        : signature       : result\n");
    printf("----------------------------------------\n");
    printf("lambda arity 0: [](){}          : %i\n", functor_traits< decltype(lambda0) >::arity );
    printf("lambda arity 1: [](int){}       : %i\n", functor_traits< decltype(lambda1) >::arity );
    printf("lambda arity 2: [](int,int){}   : %i\n", functor_traits< decltype(lambda2) >::arity );
    printf("func arity   0: void()          : %i\n", functor_traits< function<void()> >::arity );
    printf("func arity   1: int(int)        : %i\n", functor_traits< function<void(int)> >::arity );
    printf("func arity   2: char(int,int)   : %i\n", functor_traits< function<void(int,int)> >::arity );
    printf("C::operator()() arity 0         : %i\n", functor_traits< decltype(oper0) >::arity );
    printf("C::operator()(int) arity 1      : %i\n", functor_traits< decltype(oper1) >::arity );
    printf("C::operator()(int,int) arity 2  : %i\n", functor_traits< decltype(oper2) >::arity );
///////////////////////////////////////////////////////////////////////////
// Testing the bind arity below:
///////////////////////////////////////////////////////////////////////////
    printf("bind arity   0: void()          : %i\n", functor_traits< decltype(bind0) >::arity );
    printf("bind arity   1: int(int)        : %i\n", functor_traits< decltype(bind1) >::arity );
    printf("bind arity   2: void(int,int)   : %i\n", functor_traits< decltype(bind2) >::arity );
    printf("bind arity   X: void(int, 1 )   : %i\n", functor_traits< decltype(bindpartial) >::arity );

    return 0;
}

この実装は libstdc++ を使用して gcc で動作しますが、std::bind() の結果を分解しようとするため、これが移植可能なソリューションであるかどうかはよくわかりません。 libstdc++ のユーザーとして行う必要はありません。

だから私の質問は次のとおりです: std::bind() の結果を分解せずに、バインド結果のアリティをどのように決定できますか? 制限付き引数を可能な限りサポートする function_traits の完全な実装をどのように実装できますか ?

4

1 に答える 1