(Note: As should already be clear from the tags, this is strictly C++03. Yes, I know, lambda makes all this pain go away (and brings in new kinds, I bet), but this is an embedded system, with an OS version from the 90s, and I am told I should be glad I have a C++03 compiler (GCC4.1.x, BTW), or a C++ compiler at all. So please abstain from posting C++11 solutions. No need to rub it in, really.
Also, std::bind()
, std::function()
etc. are, of course, actually in std::tr1
, but I edited out the tr1
prefix, because I thought it adds mostly only noise to the code.)
I have some server-like thing that I need to register functions with and I need to adapt them to call some object's similar, but slightly different, functions. These functions have different argument lists. The server "knows" that and when I try to register a function it only accepts one with the correct signature (as correct as std::function
requires, that is), depending on some magic tag passed in as a template argument.
Here's a sketch of the code:
// this I just use
class server {
public:
template<unsigned int MagicTag>
bool register_call(typename some_traits_type<MagicTag>::func_type);
};
// this needs to be called from the server
class X {
public:
bool foo();
bool bar(std::string&);
bool baz(int);
};
// this is the glue
class Y {
public:
Y(X& x) : x_(x) {
register_call<MAGIC_FOO>(&Y::foo );
register_call<MAGIC_BAZ>(&Y::bar, _1);
register_call<MAGIC_FBZ>(&Y::baz, _1);
}
private:
X& x_;
template<unsigned int MagicTag, typename Function>
bool register_call(Function function) {
somewhere->register_call<MagicTag>(std::bind( function
, this ));
}
template<unsigned int MagicTag, typename Function, typename PlaceHolder1>
bool register_call(Function function, PlaceHolder1 place_holder1) {
somewhere->register_call<MagicTag>(std::bind( function
, this
, place_holder1 ));
}
int foo() {return x_.foo() ? MAGIC_OK : MAGIC_FAILED;}
int bar(std::string& s) {return x_.bar(s) ? MAGIC_OK : MAGIC_FAILED;}
int baz(int i) {return x_.baz(i) ? MAGIC_OK : MAGIC_FAILED;}
};
This actually works, but in reality there are way more functions and doing this as a tedious copy'n'paste effort insults my sense of dignity and produces smelly code. Since all these functions do exactly the same, with the only difference being the function they call and the arguments they have, or do not have, to pass, I should be able to fold them into one parametrized function, hiding the differences behind std::bind()
. Failing this, I settled on first doing this for all the functions without any parameters (as in foo()
), which is the overwhelming majority.
So I wanted to route all the calls of foo()
-like function in X
through a single function Y::call_it
that does the tedious part:
int call_it(std::function<bool()> f) {return f() ? MAGIC_OK : MAGIC_FAILED;}
and bind the appropriate function in X
as an argument to it:
register_call<MAGIC_FOO>(&X::foo); // note the X!
// (this is wrong)
somewhere->register_call<MagicCode>( std::bind( std::bind( &Y::call_it
, this
, function )
, std::ref(x_) );
Obviously, this is wrong, and so are all my other attempts at solving this. (I have only been playing with std::bind()
for 10 weeks now, so please bear with me). In the end I got lost in an incredible maze of hilarious error messages out of std::function
's templatized guts that can bring a grown man down in tears and should feed a shrink and his extended family for a year at least.
So before I kill myself out of sheer frustration and orphan my kids — how can I do this?
From what I gather, you want to call Y::call_it()
with a std::function
object appropriately bound. Given that your inner function takes different numbers of arguments, it is necessary to create a std::function<bool()>
generator for the cases where additional arguments are being passed. Assuming the X
object can be bound at registration time, registration of a member function without additional arguments is straight forward:
template <int Magic, typename RC>
void register_call(RC (X::*member)()) {
somewhere->register_call<Magic>(
std::bind(&Y::call_it, this,
std::function<bool()>(std::bind(member,
std::ref(this->x_)))));
}
When passing one or more arguments, it is necessary to create a std::function<bool()>
object upon call time because the additional argument needs to be bound. I don't think that this can be done without a helper function but it can be done with one helper function per number of arguments:
template <typename RC, typename Arg0>
static std::function<bool()>
bind_argument(RC (X::*member)(Arg0), X& x, Arg0 const& arg0) {
return std::bind(member, std::ref(x), arg0);
}
template <int Magic, typename RC,
typename Arg0, typename PlaceHolder>
void register_call(RC (X::*member)(Arg0), PlaceHolder pc) {
somewhere->register_call<Magic>(
typename some_traits_type<Magic>::type(
std::bind(&Y::call_it, this,
std::bind(&bind_argument<RC, Arg0>, member,
std::ref(this->x_), pc))));
}
The helper function creates a function with an additional argument being bound. Note that the function being bound is constructed to be of the same type as the one expected by the register function: This is necessary, e.g., to create a function taking additional, ignored, arguments.
Below is a test program I used to see if things compile. I don't have a C++2003 compiler with TR1 at hand and have compiled the code with a C++2011 compiler. However, I don't think I have used any of the C++2011 extension which isn't available from C++2003 with TR1.
#include <functional>
enum {
MAGIC_OK,
MAGIC_FAILED,
MAGIC_FOO,
MAGIC_BAR,
MAGIC_FBZ,
MAGIC_BAZ
};
template <int> struct server_traits;
template <> struct server_traits<MAGIC_FOO> {
typedef std::function<bool()> type;
};
template <> struct server_traits<MAGIC_BAR> {
typedef std::function<bool(std::string&)> type;
};
template <> struct server_traits<MAGIC_FBZ> {
typedef std::function<bool(long)> type;
};
template <> struct server_traits<MAGIC_BAZ> {
typedef std::function<bool(std::string, long)> type;
};
// this I just use
class server {
public:
template<unsigned int MagicTag>
bool register_call(typename server_traits<MagicTag>::type) {
return true;
}
};
server s;
server* somewhere = &s;
// this needs to be called from the server
class X {
public:
bool foo() { return true; }
bool bar(std::string&) { return true; }
bool baz(int) { return true; }
};
// this is the glue
class Y {
public:
Y(X& x) : x_(x) {
register_call<MAGIC_FOO>(&X::foo );
register_call<MAGIC_BAR>(&X::bar, std::placeholders::_1);
register_call<MAGIC_FBZ>(&X::baz, std::placeholders::_1);
register_call<MAGIC_BAZ>(&X::baz, std::placeholders::_2);
}
private:
X& x_;
int call_it(std::function<bool()> f) {
return f() ? MAGIC_OK : MAGIC_FAILED;
}
template <int Magic, typename RC>
void register_call(RC (X::*member)()) {
somewhere->register_call<Magic>(
std::bind(&Y::call_it, this,
std::function<bool()>(std::bind(member,
std::ref(this->x_)))));
}
template <typename RC, typename Arg0>
static std::function<bool()>
bind_argument(RC (X::*member)(Arg0), X& x, Arg0 const& arg0) {
return std::bind(member, std::ref(x), arg0);
}
template <int Magic, typename RC,
typename Arg0, typename PlaceHolder>
void register_call(RC (X::*member)(Arg0), PlaceHolder pc) {
somewhere->register_call<Magic>(
typename server_traits<Magic>::type(
std::bind(&Y::call_it, this,
std::bind(&bind_argument<RC, Arg0>, member,
std::ref(this->x_), pc))));
}
};
int main()
{
X x;
Y y(x);
}
From the chat channel link, it looks like you've boiled the problem down to the fact that a nested bind doesn't compile:
bind( &Y::call_it, this, bind( &X::foo, ref(x_) ) )
because the compiler can't deduce the type signature of the inner bind() (as function< bool()> in this case). This might work instead:
bind( &Y::call_it, this, function<bool()>( bind( &X::foo, ref(x_) ) ) )
and if it does, you'd have something like
template<unsigned int MagicTag, typename Function >
bool register_call(Function func) {
somewhere->register_call<MagicTag>(
bind( &Y::call_it, this, function<bool()>( bind( func, ref(x_)))));
}
though I get the feeling that the second template parameter may not be necessary somehow. The key idea is to put a std::function between the two std::bind's.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With