It's possible to define a single Logger<int, const char*>
type, which may call either a member or a non-member function of any class. To do this, you'll need to remove the Class
parameter from Logger
and instead store an opaque object pointer [void*
] and a function pointer that accepts an opaque object [R (*func)(void* object, A a)
].
This solves the problem in your question by making Logger
unaware of what kind of function it contains; whether it be a a non-member, a member of class X
, or a member of class Y
.
You can implement this using a technique I developed for C++03 which involves generating wrapper functions (aka 'thunks') to call the member and non-member functions through a function pointer known at compile-time. You can think of this as a cut-down specialised version of std::function
in C++11 or Delegate in C#.
template<typename F>
struct FunctionTraits;
template<typename R, typename C, typename A>
struct FunctionTraits<R (C::*)(A)> // matches a pointer to member function
{
typedef R RetType;
typedef C Class;
typedef A Arg1Type;
};
template<typename R, typename A>
struct FunctionTraits<R (*)(A)> // matches a pointer to function
{
typedef R RetType;
typedef A Arg1Type;
};
template<typename RetType, typename Arg1Type>
class Logger
{
typedef RetType(*Func)(void*, Arg1Type);
public:
Logger(void* pars, Func func) : pars(pars), func(func)
{
}
RetType operator()(Arg1Type a) const
{
// call the function with the opaque object
return func(pars, a);
}
private:
Func func; // a pointer to a function accepting an opaque object
void* pars; // a pointer to an opaque object
};
template<typename F, F p>
typename FunctionTraits<F>::RetType callMember(void* c, typename FunctionTraits<F>::Arg1Type a)
{
// restore the type of the object
return (static_cast<typename FunctionTraits<F>::Class*>(c)->*p)(a);
}
template<typename F, F p>
Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
makeLogger(typename FunctionTraits<F>::Class* pars)
{
typedef typename FunctionTraits<F>::RetType RetType;
typedef typename FunctionTraits<F>::Arg1Type Arg1Type;
// generates a 'thunk' function which calls the member 'p'
return Logger<RetType, Arg1Type>(pars, &callMember<F, p>);
}
template<typename F, F p>
typename FunctionTraits<F>::RetType callNonMember(void*, typename FunctionTraits<F>::Arg1Type a)
{
// the first parameter is not used
return (p)(a);
}
template<typename F, F p>
Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
makeLogger()
{
typedef typename FunctionTraits<F>::RetType RetType;
typedef typename FunctionTraits<F>::Arg1Type Arg1Type;
// generates a 'thunk' function which calls the non-member 'p'
return Logger<RetType, Arg1Type>(0, &callNonMember<F, p>);
}
int log(const char*);
struct Parser
{
int log(const char*);
};
struct OtherParser
{
int log(const char*);
};
int main()
{
Logger<int, const char*> nonmember = makeLogger<decltype(&log), &log>();
int result1 = nonmember("nonmember"); // calls log("nonmember");
Parser pars;
Logger<int, const char*> member = makeLogger<decltype(&Parser::log), &Parser::log>(&pars);
int result2 = member("member"); // calls pars.log("member");
OtherParser other;
Logger<int, const char*> member2 = makeLogger<decltype(&OtherParser::log), &OtherParser::log>(&other);
int result3 = member2("member2"); // calls other.log("member2");
}
Despite using void*
, this technique is both type-safe and standard-compliant.
In contrast with std::function
, the generated functions are able to inline the calls through the member/non-member pointer because the pointer is known at compile time.
EDIT:
The above example uses C++11's decltype to automatically determine the type of a function pointer, but this is not essential - I can offer a C++98 compatible technique that achieves the same thing:
template<typename F>
struct NonMemberHelper
{
template<F p>
static Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
apply()
{
return makeLogger<F, p>();
}
};
template<typename F>
NonMemberHelper<F> makeNonMemberHelper(F)
{
return NonMemberHelper<F>();
}
template<typename F>
struct MemberHelper
{
template<F p>
static Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
apply(typename FunctionTraits<F>::Class* pars)
{
return makeLogger<F, p>(pars);
}
};
template<typename F>
MemberHelper<F> makeMemberHelper(F)
{
return MemberHelper<F>();
}
#define MAKE_LOGGER_NONMEMBER(func) makeNonMemberHelper(func).apply<(func)>()
#define MAKE_LOGGER(func, pars) makeMemberHelper(func).apply<(func)>(pars)
int main()
{
Logger<int, const char*> callNonMember = MAKE_LOGGER_NONMEMBER(&log);
int result1 = callNonMember("nonmember"); // calls log("nonmember");
Parser pars;
Logger<int, const char*> callMember = MAKE_LOGGER(&Parser::log, &pars);
int result2 = callMember("member"); // calls pars.log("member");
}