绑定可变参数成员函数

3
所以这里的情况是:我有两个使用CRTP进行静态继承的类。基类具有调用变参模板的派生方法的run方法,以使参数灵活。现在派生类包含一个函数对象。派生类具有由基类调用的实现。可能看起来不必要,但在这段代码的完整版本中,运行的不仅仅是包含的函数。接下来有一个方法,将函数绑定到CrtpBase :: Run 方法的所有变参参数和实例,从而将函数转换为bool(void)函数。这就是我遇到问题的地方。我尝试了两种不同的方法,使用lambda的版本已被注释掉。两种方法都不起作用。我的目标是让VoidFunction绑定所有参数,以便我可以在没有参数的情况下随意执行该函数。我在这里做错了什么?
#include <functional>
#include <utility>

template <typename D>
struct CrtpBase {
  template <typename ... Args>
  bool Run(Args&& ... args) const {
    return static_cast<D&>(*this).Impl(std::forward<Args>(args) ...);
  }
};

template <typename ... Args>
struct CrtpDerived : public CrtpBase<CrtpDerived<Args ...>> {
  CrtpDerived(std::function<bool(Args ...)> function) : runable(std::move(function)) {}

  bool Impl(Args&& ... args) const {
    return this->runable(std::forward<Args>(args) ...);
  }

  std::function<bool(Args ...)> runable;
};

template <typename D, typename ... Args>
std::function<bool()> VoidFunction(CrtpBase<D> base, Args&& ... args) {
//  return [&base, &args ...]()->bool{return CrtpBase<D>::template Run<Args ...>(base);};
  return std::bind(CrtpBase<D>::template Run<Args ...>, base, std::forward<Args>(args) ...);
}

int main(int argc, char** argv) {
  std::function<bool(int&)> fn = [](int& a)->bool{a /= 2; return (a % 2) == 1;};
  CrtpDerived<int&> derived(fn);
  int x = 7;
  auto voided = VoidFunction(derived, x);
  bool out = voided();
  if ((x == 3) and (out == true)) {
    return EXIT_SUCCESS;
  } else {
    return EXIT_FAILURE;
  }
}

修订:

  1. 修正最终测试中的拼写错误,(out == false) 改为 (out == true)
1个回答

1
首先,从编译器的角度来看,CrtpBase<D>::template Run<Args ...> 是一个无意义/不完整的标记组合。在C++中没有这样的表达式语法。这看起来像是尝试形成指向成员的指针,但这需要显式应用 & 运算符。
return std::bind(&CrtpBase<D>::template Run<Args ...>, base, std::forward<Args> (args) ...);

其次,这个 cast
static_cast<D&>(*this)

尝试去掉常量属性的操作在static_cast中是不被允许的。
第三点,你的
std::bind(&CrtpBase<D>::template Run<Args ...>, base, std::forward<Args> (args) ...);

将隐含的this参数绑定到函数参数base。这样做是行不通的,因为base会在VoidFunction退出时(或调用表达式结束时)被销毁。
@aschepler在评论中正确指出,将base作为CrtpBase<D>值传递会切割原始的CrtpDerived<int&>对象。通过引用传递它,然后使用&base作为std::bind的参数。
第四,std::bind不会按引用绑定,也无法借助std::forward来解决此问题。这意味着你lambdafn内的a不会绑定到x。使用std::ref来解决这个限制。
#include <functional>
#include <utility>

template <typename D>
struct CrtpBase {
  template <typename ... Args>
  bool Run(Args&& ... args) const {
    return static_cast<const D&>(*this).Impl(std::forward<Args>(args) ...);
  }
};

template <typename ... Args>
struct CrtpDerived : public CrtpBase<CrtpDerived<Args ...>> {
  CrtpDerived(std::function<bool(Args ...)> function) : runable(std::move(function)) {}

  bool Impl(Args&& ... args) const {
    return this->runable(std::forward<Args>(args) ...);
  }

  std::function<bool(Args ...)> runable;
};

template <typename D, typename ... Args>
std::function<bool()> VoidFunction(CrtpBase<D> &base, Args&& ... args) {
  return std::bind(&CrtpBase<D>::template Run<Args ...>, &base, std::forward<Args>(args) ...);
}

int main(int argc, char** argv) {
  std::function<bool(int&)> fn = [](int& a)->bool { a /= 2; return (a % 2) == 1; };
  CrtpDerived<int&> derived(fn);
  int x = 7;
  auto voided = VoidFunction(derived, std::ref(x));
  bool out = voided();
  if ((x == 3) && (out == false)) {
    return EXIT_SUCCESS;
  } else {
    return EXIT_FAILURE;
  }
}

最后一件事:我不明白为什么你希望最终out的值为false


那么,我该如何解决这个问题呢?我可以将其静态转换为const D&。这很有道理。它可以编译通过。但是当它运行时,它会抛出一个std::bad_function_call实例,所以仍然存在问题。 - esdanol
我猜测我遇到std::bad_function_call的问题是因为base已经被销毁了。这是为什么,我该如何解决? - esdanol
1
你提到了base被销毁,并在修复后的代码中将其更正为引用,但我还要指出发生在非引用基类变量中的对象切片。 - aschepler
什么是切片? - esdanol
@esdanol: https://dev59.com/YHVC5IYBdhLWcg3wfxQ8。在您的原始代码中,`VoidFunction`的第一个参数类型为`CrtpBase<D>。而您正在将CrtpDerived<int&> derived作为参数传递。会发生的是,将传递derivedCrtpBase<D>子对象的副本,而不是整个derived对象(这就是“切片”)。稍后,尝试将该base强制转换为CrtpDerived<int&>将仅导致未定义行为,因为该base不再是任何CrtpDerived<int&>`的一部分。 - AnT stands with Russia
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接