2

Sometimes I need to bind some member functions to its calling object, to treat member functions and non-member functions in the same homogeneous way. For example (The tipical callback example):

#include <vector>
#include <functional>

void f(int){}

struct foo
{
    void f(int){}
};

int main()
{
    using namespace std::placeholders;

    foo my_foo;
    std::vector<std::function<void()>> functions;

    functions.push_back( f );
    functions.push_back([](int){});
    functions.push_back( std::bind( &foo::f , my_foo , _1 ) );


    for( auto& function : functions )
    {
        function(0);
    }
}

As more parameters the member function has, more placeholders we need to put inside the std::bind() call.

Now consider a generic version of that. There shouldn't be problem, isn't?:

#include <vector>
#include <functional>

void f(int){}

struct foo
{
    void f(int){}
};

template<typename FIRST , typename SECOND , typename THIRD>
class callback_list
{
    using callback_t = std::function<void(FIRST,SECOND,THIRD>)>;



    //Overload for non-member handlers:
    void add( const callback_t& handler )
    {
        _handlers.push_back( handler );
    }

    //Overload for member handlers:
    template<typename CLASS>
    void add( CLASS& object_ref , 
                      void(CLASS::*member_function)( FIRST,SECOND,THIRD ) )
    {
        using namespace std::placeholders;

        _handlers.push_back( std::bind( member_function , 
                                        std::ref( object_ref ) , 
                                        _1 , _2 , _3
                                      ) 
                           );
    }

    template<typename... ARGS>
    void operator()( ARGS&&... args )
    {
        for( auto& handler : _handlers )
            handler( std::forward<ARGS>( args )... );
    } 

private:
    std::vector<callback_t> functions;
};


void f(int,int,int){}

struct foo
{
    void f(int,int,int){}
};

int main()
{
    using namespace std::placeholders;

    foo my_foo;
    callback_list<int,int,int> callbacks;

    callbacks.add( f );
    callbacks.add([](int,int,int){});
    callbacks.add( my_foo , &foo::f );

    callbacks(0,0,0);
}

Ok. The add() overload for member callbacks just binds the object to the member function, and because the callbacks are of three parameters, we use three placeholders.

But consider this: What if the callbacks have any number of parameters?.
In other words, what I have to do if the callback_list class template is defined with a variadic template?:

template<typename... ARGS>
class callback_list{ ... };

How can I bind a variadic function with any function parameter known at the point of the std::bind() call, i.e., with an unspecified number of placeholders?

Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • 1
    Related? http://stackoverflow.com/q/21192659/420683 – dyp Feb 09 '14 at 18:30
  • @dyp LOL I have checked [this](http://stackoverflow.com/questions/5608606/callback-with-variadic-template-to-ambiguous-overloads), [this](http://stackoverflow.com/questions/14803112/short-way-to-stdbind-member-function-to-object-instance-without-binding-param), [this](http://stackoverflow.com/questions/11372855/stdbind-to-stdfunction), and [this](http://stackoverflow.com/questions/18380820/how-to-combine-stdbind-variadic-templates-and-perfect-forwarding) but I have not finded that thread... Thanks a lot. – Manu343726 Feb 09 '14 at 18:34
  • Well the [lambda version](http://stackoverflow.com/a/14803335/420683) would work as well, wouldn't it? – dyp Feb 09 '14 at 18:36
  • @dyp yes, that solution works and I'm currently testing it, but I'm specially interested on `std::bind()` and how to make this working with it. – Manu343726 Feb 09 '14 at 18:41
  • Shall I mark it as a duplicate or [post this as an answer](http://coliru.stacked-crooked.com/a/1d8a5563ab772d52)? – dyp Feb 09 '14 at 18:48
  • @dyp Post the answer please. It could help me (and other readers) to review and understand the code in the context of this question, not other. OTOH, if anyone thinks this is a duplicate, flag it later. – Manu343726 Feb 09 '14 at 19:06

1 Answers1

3

To work with std::bind, we need to somehow supply a certain amount of placeholders, depending on the amount of function parameters of the callback. I've described a way here how you can do that, by creating a generator for placeholders:

template<int> struct placeholder_template {};

By partially specializing std::is_placeholder for the above template, std::bind sees the instantiations placeholder_template<N> as placeholder types. With the usual indices trick, we then expand placeholder_template<0>{}, placeholder<1>{}, ...., placeholder<N-1>{}, where N is the number of function parameters.

template<typename... Params>
class callback_list
{
public:
    using callback_t = std::function<void(Params...)>;

    //Overload for non-member handlers:
    void add( const callback_t& handler )
    {
        _handlers.push_back( handler );
    }

private:
    //Overload for member handlers:
    template<typename CLASS, int... Is>
    void add( CLASS& object_ref , 
              void(CLASS::*member_function)( Params... ) ,
              int_sequence<Is...> )
    {
        using namespace std::placeholders;

        _handlers.push_back( std::bind( member_function , 
                                        std::ref( object_ref ) , 
                                        placeholder_template<Is>{}...
                                      ) 
                           );
    }
public:
    template<typename CLASS>
    void add( CLASS& object_ref , 
              void(CLASS::*member_function)( Params... ) )
    {
        add( object_ref, member_function,
             make_int_sequence<sizeof...(Params)>{} );
    }
    

    template<typename... ARGS>
    void operator()( ARGS&&... args )
    {
        for( auto& handler : _handlers )
            handler( std::forward<ARGS>( args )... );
    } 

private:
    std::vector<callback_t> _handlers;
};

The code for the placeholder generator and the integer sequence, from the other answer:

template<int...> struct int_sequence {};

template<int N, int... Is> struct make_int_sequence
    : make_int_sequence<N-1, N-1, Is...> {};
template<int... Is> struct make_int_sequence<0, Is...>
    : int_sequence<Is...> {};

template<int> // begin with 0 here!
struct placeholder_template
{};

#include <functional>
#include <type_traits>

namespace std
{
    template<int N>
    struct is_placeholder< placeholder_template<N> >
        : integral_constant<int, N+1> // the one is important
    {};
}

Using C++14, one can use std::index_sequence_for<Params...> instead of custom make_int_sequence<sizeof...(Params)>.


Side remark: If you want to accept member functions with cv- and ref-qualifiers, you could use a very general "pattern" like

template<class C, class T>
void add(C& ref, T fun);

and restrict via SFINAE. Here's a trait that lets you deduce the number of parameters from such a function pointer (via tuple_size).

darkdragon
  • 392
  • 5
  • 13
dyp
  • 38,334
  • 13
  • 112
  • 177
  • Thanks, finally it worked. Only one question: I can't assign the result of bind (i.e. the bind proxy which `std::bind()` returns) to a `std::function`, which of course normally works. Can this bind playing change the signature of the result proxy and then break the conversion? – Manu343726 Feb 10 '14 at 23:03
  • 1
    @Manu343726 Isn't this what I'm doing in my [live example](http://coliru.stacked-crooked.com/a/1d8a5563ab772d52)? I had to look into the Std again to see if it's really required to work, but to me, it sounds like a library defect. Which library implementation have you used? – dyp Feb 11 '14 at 18:05
  • This could be simplified using [`std::index_sequence_for`](https://en.cppreference.com/w/cpp/utility/integer_sequence). – darkdragon Dec 11 '20 at 18:39
  • Furthermore, it should be possible to use `std::integral_constant{}...` instead of `placeholder_template{}...`. This way, we could get rid of the 3rd code snippet completely! – darkdragon Dec 11 '20 at 18:49
  • @darkdragon The first one I understand, though `index_sequence_for` wasn't available in C++11. The second one, using `integral_constant` instead of `placeholder_template` - this I don't get: I'm not aware of any existing template that would be counted as a placeholder... – dyp Dec 14 '20 at 14:36
  • @dyp [`std::placeholders`](https://en.cppreference.com/w/cpp/utility/functional/placeholders) defines `std::is_placeholder` for `std::integral_constant`: _for any placeholder `_N`, the type `std::is_placeholder` is defined and is derived from `std::integral_constant`._ – darkdragon Dec 15 '20 at 13:27
  • 1
    @darkdragon ... it's _derived_ from `integral_constant`. The way I understand it: `is_placeholder` is a metafunction that maps `T -> X`, where `X` is derived from ("base characteristic") some `integral_constant`. So, `bind` checks for each argument if `is_placeholder` is such that `N > 0`. But I don't see where `is_placeholder>` is defined (such that `N == M`). – dyp Dec 15 '20 at 16:52
  • @darkdragon One could add `namespace std { template struct is_placeholder> : integral_constant {}; }`, but that's not allowed, since you're only allowed to specialize std-templates on user-defined types. – dyp Dec 15 '20 at 16:54
  • @dyp You seem right. Too bad that such functionality isn't included in the std library directly... – darkdragon Dec 20 '20 at 10:23