Search code examples
c++c++14wrappervariadic-templatestemplate-argument-deduction

Trouble deducing return type for wrapper of member functions


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


Solution

  • 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 ints are converted to longs.