【问题标题】:Trouble deducing return type for wrapper of member functions无法推断成员函数包装器的返回类型
【发布时间】:2019-01-30 18:24:16
【问题描述】:

我有一组具有不同签名的方法,它们都需要相同的前缀和后缀代码,所以我想把它们都整齐地包装起来。 我正在尝试为我的成员函数的通用包装器制定一个 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_ofdecltype() 的许多变体,但我无法正确编译

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

我期待找到一个解决方案,其中FnArgs... 的签名被正确识别,因为auto 也成功地推断出类型。我的类型声明似乎没有找到匹配的模板,但无论我尝试了何种变体,我都得到了

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

如果我将包装器的返回类型保留为 auto 并尝试在其中声明变量 ret,我会得到 ​​p>

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&amp;(Args... ) 是格式良好的,但我不知道正确的格式应该是什么样子。

任何帮助将不胜感激

【问题讨论】:

  • 如果你使用auto作为函数返回类型,没有尾随返回类型,你使用的是C++14,而不是C++11

标签: c++ c++14 wrapper variadic-templates template-argument-deduction


【解决方案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;
   } 

-- 编辑--

OP 询问

但您能否解释一下为什么在您的第一个解决方案中需要两个单独的参数集?

两个单独的参数集不是严格要求的,但这提供了更大的灵活性。

假设你有一个集合

  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) 

现在编译器必须从foo()1, 2 推导出As...

foo(),编译器将As...推​​导出为long, long

1, 2,编译器将As...推导出为int, int

So:编译错误,因为编译器有推演冲突。

对于双组类型(As1...As2...),编译器将As1... 推导出为long, longAs2...int, int。没有冲突所以没有编译错误。

使用int 值调用foo() 也没有问题,因为ints 被转换为longs。

【讨论】:

  • 非常感谢,这两种方法都很有效!我想我得到了 std::result_of 代码,但是您能否澄清一下为什么在您的第一个解决方案中需要两个单独的参数集?这两个集合是相等的,我也可以通过将包装函数调用中的类型更改为 As1 来证明这一点。所以我的猜测是,只有当成员函数指针的参数类型集合和我之后提供的参数类型集合是不同的集合时,才能正确填写模板。仅仅因为它们碰巧相等,这不允许一般重用。这是正确的吗?
  • @Samuraiburger - 答案改进;希望这会有所帮助。
  • 谢谢!很好的解释,它使一分钱下降。这是一个漂亮而优雅的解决方案。这在编译器推断类型时效果很好,但是当我需要显式提供模板参数以消除重载成员函数的歧义时,我仍然感到有些遗憾。我可以很容易地编译它,但它确实会导致我无法弄清楚的链接器错误。如果我无法再取得进展,我将进行更多实验并为此打开一个新线程并交叉引用当前线程。再次感谢队友
  • 结果不是模板错误。链接为共享库时似乎与问题有关。至此,原模板问题彻底解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-24
  • 2013-08-25
  • 2021-03-04
  • 2018-08-22
相关资源
最近更新 更多