I've been pounding my head for a few days trying to figure out how to make a class have a nice clean public interface to perform registration of callback mechanisms. The callbacks can be C++11 lambdas, std::function<void(Type1,Type2)>, std::function<void(Type2)>, std::function<void()>, or the results of std::bind().
The key to this interface is that the user of the class only needs to know about one public interface that accepts pretty much any functor/callback mechanism the user might throw at it.
Simplified class showing registration of functors and interface
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
The only reason to have the complexity of using enable_if<> is to turn on/off certain methods. We have to do this because when we want to pass in the results of std::bind() to the Register() method and it can ambiguously match against multiple registration methods if we had simple signatures like this:
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 );
Rather than re-invent the wheel, I've referenced traits.hpp and then wrapped it with my own trait helper named "functor_traits" that adds support for std::bind()
This is what I've come up with so far to identify bounded function "arity" ... or a count of how many arguments the bind result expects as a :
My attempt at finding the bind result arity
#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;
}
While this implementation works in gcc with libstdc++, I'm not quite sure if this is a portable solution since it tries to break apart the results of std::bind() ... The nearly private "_Bind" class that we really shouldn't need to do as users of libstdc++.
So my questions are: How can we determine the arity of bind results without decomposing the result of std::bind()? and How can we implement a full implementation of function_traits that supports bounded arguments as much as possible?
OP, your premises are flawed. You're looking for some kind of routine that can tell you, for any given object
x, how many argumentsxexpects — that is, which ofx(),x(a), orx(a,b)is well-formed.The problem is that any number of those alternatives might be well-formed!
In a discussion on isocpp.org of this very topic, Nevin Liber very correctly writes:
Here's a concrete example.
So the only interface we can actually implement is one where we supply an actual argument count, and ask whether
xcan be called with that number of arguments....But what if it can be called with one argument of type
Fooor two arguments of typeBar? It seems that just knowing a (possible) arity ofxisn't useful — it doesn't really tell you how to call it. To know how to callx, we need to know more or less what types we're trying to pass to it!So, at this point, the STL comes to our rescue in at least one way:
std::result_of. (But see here for a saferdecltype-based alternative toresult_of; I use it here only for convenience.)Now we can ask questions like
can_be_called_with_one_int_t<int(*)(float)>orcan_be_called_with_one_int_t<int(*)(std::string&)>and get reasonable answers.You could construct similar traits classes for
can_be_called_with_no_arguments,...with_Type2,...with_Type1_and_Type2, and then use the results of all three of those traits to build up a complete picture of yourx's behavior — at least, the part ofx's behavior that is relevant to your particular library.