Проблема с определением типа возвращаемого значения для оболочки функций-членов

У меня есть набор методов с разными сигнатурами, для которых нужен один и тот же префиксный и постфиксный код, поэтому я хотел бы аккуратно обернуть каждый из них. Я пытаюсь сформулировать вариативный шаблон C ++ 14 для общей оболочки для моих функций-членов, и у меня возникают проблемы при выводе типа возвращаемого значения. auto работает, но мне нужен возвращаемый тип явно, чтобы предоставить допустимое возвращаемое значение, даже если я поймаю исключение внутри оболочки при выполнении wrappee.

Пока мне удалось определить тип возвращаемого значения для простой функции-оболочки, используя std::result_of, и я получил правильное поведение для нестатических функций-членов, используя auto. В течение двух дней я пытался заставить подход std::result_of работать для функций-членов, также пробовал множество вариантов decltype() и std::declval(), но пока безуспешно. У меня заканчиваются идеи, как определить возвращаемый тип.

Это мой рабочий пример

#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;
}

Оба они работают хорошо, но метод оболочки, использующий auto, не дает мне возвращаемого типа для дальнейшего использования. Я пробовал множество вариантов с std::result_of и decltype() со следующим кодом, но не могу заставить его правильно скомпилировать

#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;
}

Я ожидал найти решение, в котором подпись Fn с Args... распознается правильно, потому что auto также успешно определяет типы. Мое объявление типа, похоже, не находит подходящий шаблон, хотя, независимо от того, какие варианты я пробовал, я получаю

error: no matching function for call to ‘MemberWithDecl::wrapper(int (MemberWithDecl::*)(int, int), int, int)’

Если я оставлю автоматический тип возвращаемого значения оболочки и просто попробую объявить переменную ret внутри, я получу

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)...);

После прочтения стандарта я думаю, что это означает, что result_of не считает Fn&(Args... ) правильно сформированным, но я не знаю, как должна выглядеть правильная форма.

Любая помощь приветствуется


person Samuraiburger    schedule 30.01.2019    source источник
comment
если вы используете auto в качестве возвращаемого типа функции без конечного возвращаемого типа, вы используете C ++ 14, а не C ++ 11   -  person max66    schedule 30.01.2019


Ответы (1)


Вам не нужно std::result_of; вы можете просто написать

  template <typename R, typename ... As1, typename ... As2>
  R wrapper (R(MemberWithDecl::*fn)(As1...), As2 ... args)
   { return (*this.*fn)(std::forward<As2>(args)...); } 

Если вы действительно хотите использовать std::result_of, вам нужно добавить указатель MemberWithDecl (тип this) в качестве первого аргумента.

  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;
   } 

- ИЗМЕНИТЬ -

ОП спросить

но не могли бы вы прояснить, почему в вашем первом решении необходимы два отдельных набора параметров?

Два отдельных набора параметров не требуются строго, но это дает большую гибкость.

Предположим, у вас есть один набор

  template <typename R, typename ... As>
  R wrapper (R(MemberWithDecl::*fn)(As...), As ... args)
   { return (*this.*fn)(std::forward<As>(args)...); } 

и учитывая

 long foo (long a, long b) { return a + b; }

предположим, вы звоните

 wrapFunc(foo, 1, 2) 

Теперь компилятор должен вывести As... из foo() и из 1, 2.

Из foo() компилятор выводит As... как long, long.

Из 1, 2 компилятор выводит As... как int, int.

Итак: ошибка компиляции, потому что у компилятора есть конфликт вывода.

С двойным набором типов (As1... и As2...) компилятор выводит As1... как long, long и As2... как int, int. Нет конфликта, поэтому нет ошибки компиляции.

И также нет проблем с вызовом foo() со значениями int, потому что ints преобразованы в longs.

person max66    schedule 30.01.2019
comment
Большое спасибо, оба подхода работают как шарм! Я думаю, что получил код std :: result_of, но не могли бы вы пояснить, почему в вашем первом решении необходимы два отдельных набора параметров? Эти два набора равны, что я тоже могу доказать, изменив тип в обернутом вызове функции на As1. Поэтому я предполагаю, что шаблон может быть правильно заполнен только в том случае, если набор типов параметров указателей функций-членов и набор типов параметров, которые я предоставляю впоследствии, являются отдельными наборами. То, что они равны, не допускает повторного использования. Это правильно? - person Samuraiburger; 31.01.2019
comment
@Samuraiburger - ответ улучшен; надеюсь это поможет. - person max66; 01.02.2019
comment
Спасибо! Очень хорошее объяснение, оно сильно упало. И это красивое и элегантное решение. Это прекрасно работает, когда компилятор определяет типы, но я все еще испытываю некоторое горе, когда мне нужно явно предоставить параметр шаблона для устранения неоднозначности перегруженных функций-членов. Я могу легко заставить его скомпилировать, но это действительно приводит к ошибкам компоновщика, которые я не могу понять. Я поэкспериментирую еще и открою для этого новую ветку и сделаю перекрестную ссылку на текущую, если я больше не смогу добиться прогресса. Еще раз спасибо дружище - person Samuraiburger; 04.02.2019
comment
‹Но я все еще испытываю некоторое горе, когда мне нужно явно указать параметр шаблона для устранения неоднозначности перегруженных функций-членов› Оказалось, что это не ошибка шаблона. Кажется, связана с проблемой при линковке в качестве общей библиотеки. Итак, проблема исходного шаблона полностью решена. - person Samuraiburger; 04.02.2019