I have a class that encapsulates the call to a member function of a class. The beauty of this encapsulation is that I can inject operations at runtime that will be executed before or after the function call itself. Very useful for, for example, associating a log to certain calls without having to modify the original class.
A fairly simplified example of the class could be this:
template<class Type, class ReturnType, class ... Args>
class FunctionWrapper
{
public:
using FuncType = ReturnType (Type::*)(Args ...);
FunctionWrapper(FuncType function)
: m_function{function}
{ }
ReturnType operator()(Type* type, Args ... args)
{
PreOperations();
ReturnType toReturn = (type->*m_function)(args...);
PostOperations();
return toReturn;
}
private:
FuncType m_function;
void PreOperations()
{ /* ... */ }
void PostOperations()
{ /* ... */ }
};
The class in general fulfills its purpose:
struct POO
{
int func1(int a)
{ return a*2; }
void func2(int a)
{ std::cout << a; }
};
int main()
{
FunctionWrapper<POO,int,int> wrapper1(&POO::func1);
POO poo;
std::cout << wrapper1(&poo,5);
}
However, it is not perfect and it fails when the function to encapsulate has void
. So the following lines:
FunctionWrapper<POO,void,int> wrapper2(&POO::func2);
wrapper2(&poo,5);
They generate a compile-time error:
error: variable has incomplete type 'void'
ReturnType toReturn = (type->*m_function)(args...);
^
The problem is that since the template is defined at the class level I can't disable the function using SFINAE:
typename std::enable_if<std::is_same<ReturnType,void>::value,void>::type
operator()(Type* type, Args ... args)
{
PreOperations();
(type->*m_function)(args...);
PostOperations();
}
typename std::enable_if<!std::is_same<ReturnType,void>::value,ReturnType>::type
operator()(Type* type, Args ... args)
{
PreOperations();
ReturnType toReturn = (type->*m_function)(args...);
PostOperations();
return toReturn;
}
The compiler already takes care of letting me know that it doesn't buy the solution:
error: failed requirement 'std::is_same<int, void>::value'; 'enable_if' cannot be used to disable this declaration
typename std::enable_if<std::is_same<ReturnType,void>::value,void>::type
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How can this problem be solved?
For these cases, the conditional constant expression is the solution:
Depending on the result of the constant expression
std::is_same
, one branch of the constant condition or the other will be compiled, avoiding the problem that the template is defined at the class level.You can see the code working in Wandbox 三へ( へ՞ਊ ՞)へ ハッハッ.
If you do not have a compiler that offers the conditional constant expression, you can opt for a less clear and intuitive solution:
This approach works because it can be returned
void
in a function whose return type isvoid
, the problem is that itPostOperations
is called before the functor and I assume that the objective is for it to be called after, for this we should create an auxiliary class that callsPreOperations
when it is built andPostOperations
when it is destroyed :And use it in the call to the parenthesis operator:
You can see the code working in Wandbox 三へ( へ՞ਊ ՞)へ ハッハッ.