I have a collection of methods with varying signatures which all need the same prefix and postfix code, so I'd like to wrap each of them up neatly.
I'm trying to formulate a C++14 variadic template for a generic wrapper to my member functions, and I hit trouble when deducing the return type. auto
works, but I need the return type explicitly to provide a valid return value even if I catch an exception inside the wrapper when executing the wrappee.
So far I managed to deduce the return type for a plain wrapper function using std::result_of
and I got correct behaviour for non static member functions using auto. I tried for two days now to make the std::result_of
approach work for member functions, also tried a lot of variations of decltype()
and std::declval()
, but with no luck so far. I am running out of ideas on how to deduce the return type.
This is my working example
#include <iostream>
int foo(int a, int b) { return a + b; }
int bar(char a, char b, char c) { return a + b * c; }
template<typename Fn, typename... Args>
typename std::result_of<Fn&(Args... )>::type
wrapFunc(Fn f, Args... args) {
//Do some prefix ops
typename std::result_of<Fn&(Args... )>::type ret = f(std::forward<Args>(args)...);
//Do some suffix ops
return ret;
}
class MemberWithAuto {
private:
public:
MemberWithAuto() {};
int foo(int i, int j) { return i + j;}
template<typename Fn, typename... Args>
auto wrapper(Fn f, Args... args) {
//Do some prefix ops
auto ret = (*this.*f)(std::forward<Args>(args)...);
//Do some suffix ops
return ret;
}
};
int main() {
std::cout << "FuncWrapper for foo with 1 + 2 returns " << wrapFunc<decltype(foo)>(foo, 1, 2) << std::endl;
std::cout << "FuncWrapper for bar with 'a' + 'b' * 1 returns " << wrapFunc<decltype(bar)>(bar, 'a','b', 1) << std::endl;
MemberWithAuto meau = MemberWithAuto();
std::cout << "MemberFunction with Auto with 6 + 1 returns " << meau.wrapper(&MemberWithAuto::foo, 6, 1) << std::endl;
return 0;
}
Both of these work well, but the wrapper method using auto doesn't get me the return type for later use.
I tried lots of variations with std::result_of
and decltype()
with the following code, but I can't get it to compile correctly
#include <iostream>
int foo(int a, int b) { return a + b; }
int bar(char a, char b, char c) { return a + b * c; }
class MemberWithDecl {
private:
public:
MemberWithDecl() {};
int foo(int i, int j) { return i + j;}
template<typename Fn, typename... Args>
typename std::result_of<Fn&(Args... )>::type wrapper(Fn f, Args... args) {
//Do some prefix ops
typename std::result_of<Fn&(Args... )>::type ret = (*this.*f)(std::forward<Args>(args)...);
//Do some suffix ops
return ret;
}
};
int main() {
MemberWithDecl medcl = MemberWithDecl();
std::cout << "MemberFunction with declaration also works " << medcl.wrapper(&MemberWithDecl::foo, 6, 1) << std::endl;
return 0;
}
I was expecting to find a solution where the signature of Fn
with Args...
is correctly recognized, because auto
also successfully deduces the types. My type declaration does not seems to find a matching template though, no matter the variations I tried, I get
error: no matching function for call to ‘MemberWithDecl::wrapper(int (MemberWithDecl::*)(int, int), int, int)’
If I leave the wrapper's return type to be auto and just try my declaration for the variable ret
within, I get
error: no type named ‘type’ in ‘class std::result_of<int (MemberWithDecl::*&(int, int))(int, int)>’
typename std::result_of<Fn&(Args... )>::type ret = (*this.*f)(std::forward<Args>(args)...);
After reading the standard, I think this means that result_of does not regard Fn&(Args... )
to be well-formed, but I don't know how the correct form should look like.
Any help would be much appreciated
You don't need std::result_of
; you can simply write
template <typename R, typename ... As1, typename ... As2>
R wrapper (R(MemberWithDecl::*fn)(As1...), As2 ... args)
{ return (*this.*fn)(std::forward<As2>(args)...); }
If you really want to use std::result_of
, you have to add a MemberWithDecl
pointer (the type of this
) as first argument.
template <typename Fn, typename ... Args>
typename std::result_of<Fn&(MemberWithDecl*, Args... )>::type
wrapper (Fn f, Args ... args)
{
typename std::result_of<Fn&(MemberWithDecl*, Args... )>::type ret
= (*this.*f)(std::forward<Args>(args)...);
return ret;
}
-- EDIT --
The OP ask
but could you perhaps clarify why two separate parameter sets are needed in your first solution?
Two separate parameter sets aren't strictly required but this gives more flexibility.
Suppose you have a single set
template <typename R, typename ... As>
R wrapper (R(MemberWithDecl::*fn)(As...), As ... args)
{ return (*this.*fn)(std::forward<As>(args)...); }
and given
long foo (long a, long b) { return a + b; }
suppose you call
wrapFunc(foo, 1, 2)
Now the compiler has to deduce As...
from foo()
and from 1, 2
.
From foo()
, the compiler deduce As...
as long, long
.
From 1, 2
, the compiler deduce As...
as int, int
.
So: compilation error because the compiler has a deduction conflict.
With a double set of types (As1...
and As2...
), the compiler deduce As1...
as long, long
and As2...
as int, int
. There isn't conflict so no compilation error.
And there isn't also problem calling foo()
with int
values because the int
s are converted to long
s.