I got this logging templated functor
template<typename RetType, typename Arg1Type, typename Class>
class Logger
{ public:
RetType operator()(Arg1Type s, ...)
{
if(func != 0 && parser != 0)
return (parser->*func)(s);
else if(nfunc != 0)
return nfunc(s);
return RetType();
}
Logger& operator=(RetType(*fun)(Arg1Type s, ...))
{
func = fun;
return *this;
}
void Bind(Class* pars, RetType(Class::*fun)(Arg1Type s,...))
{
parser = pars;
func = fun;
nfunc = 0;
}
void Bind(RetType(*fun)(Arg1Type s,...))
{
nfunc = fun;
func = 0;
parser = 0;
}
private:
RetType (Class::*func)(Arg1Type s, ...); //member class method
RetType(*nfunc)(Arg1Type s, ...); //non-member class method
Class* parser;
};
Now I can call this class using something like this :
Logger<int, const char*, WinLogger > p1;
WinLogger w1;
p1.Bind(&w1, &WinParser::Log);
p1("log");
But when I want to bind it to any non-member function using:
Logger<int, const char*, void>
the compiler complains that: 'Class': must be a class or namespace when followed by '::'. Because he cannot fit the void type to the first Bind method.
But if create the logger with any DummyClass its ok.
Logger<int, const char*, DummyClass> p2;
p2.Bind(printf);
p2("printf called");
Which is very ugly. Is there a workaround this?
I know I should probably be using boost::function etc. but I wanted to exactly learn how functon pointers and functors works so I decided to not use it.
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");
}
#include <iostream>
#include <type_traits>
template <typename T>
struct Logger
{
Logger(T func) : func(func)
{
}
template <typename... Args>
auto operator()(const Args&... params) -> decltype(std::declval<T>()(params...))
{
return func(params...);
}
T func;
};
void simple_logger(const char *mesg)
{
std::cout << "Simple: " << mesg << std::endl;
}
struct ComplexLogger
{
std::ostream& operator()(const char *mesg)
{
return std::cout << "Complex: " << mesg;
}
};
int main()
{
Logger<decltype(&simple_logger)> l1(simple_logger);
l1("hello!");
ComplexLogger cl;
Logger<ComplexLogger> l2(cl);
l2("hello!") << "yello!";
}
Output
Simple: hello!
Complex: hello!yello!
I'd seen in <algorithm>
where comparator functions (BinaryPredicate
, UnaryPredicate
, etc.) are taken as a templatized argument so that both a functor (e.g. less
) or a function pointer can be passed as a argument. I've used a similar trick here.
I don't know if it'll exactly match your case (since it expects the logging class to have operator()
defined and also this uses C++11 features) though I thought it might help.
You will definitely need some kind of default implementation for a class.
But the good news is that this can be foreseen by the logger class itself.
Just replace
template<typename RetType, typename Arg1Type, typename Class>
with
template<typename RetType, typename Arg1Type, typename Class=DefaultDummyClass>
of course the DefaultDummyClass has to be foreseen, but it no longer has to be provided by somebody who wants to use the logger class.
I'd suggest studying boost::function. I wrote an article a while back that may be some help:
http://crazyeddiecpp.blogspot.com/2010/02/implementing-stdtr1function-pt-1.html
I never got back to the task though and don't discuss member functions. It may help you to get started though.
Note that boost uses a different technique to do the same thing. They use a void function pointer instead of inheritence to avoid all the data that's created with RTTI and all that.