【问题标题】:why pass reference of a varaible to a varadic template function by universal reference operator (&&) failed?为什么通过通用引用运算符 (&&) 将变量的引用传递给可变参数模板函数失败?
【发布时间】:2019-09-02 05:10:05
【问题描述】:

我本来打算把一个变量的引用传递给一个成员函数指针,这个指针是另一个varadic模板函数的参数,用来调用一个类的任何一种成员函数,但是从打印结果来看,它并没有被传递参考,但只是按价值。

#include <functional>
#include <iostream>
#include <memory>

template <class WorkerType> class Delegate {
public:
  template <typename... Args>
  using WrkFunc = void (WorkerType::*)(Args... args);

  explicit Delegate(WorkerType &wrk) : m_worker(&wrk) {}

  template <typename... Args>
  void workerDo(WrkFunc<Args...> func, Args &&... args) {
    auto fn = std::bind(func, m_worker.get(), std::forward<Args>(args)...);
    fn();
  }

private:
  std::shared_ptr<WorkerType> m_worker;
};

class SomeWorker {
public:
  SomeWorker() = default;

  void doSomething(int &a) {
    a = 1000;
    std::cout << "2# address: " << &a << ", value: " << a << std::endl;
  }
};

int main() {
  SomeWorker wrk;
  Delegate<SomeWorker> del(wrk);
  int a = 0;
  std::cout << "1# address: " << &a << ", value: " << a << std::endl;
  del.workerDo(&SomeWorker::doSomething, a);
  std::cout << "3# address: " << &a << ", value: " << a << std::endl;
  return 0;
}

我期望的结果是这样的:
1#地址:0x7fffc1dc621c,值:0
2#地址:0x7fffc1dc621c,值:1000
3#地址:0x7fffc1dc621c,值:1000

但实际结果是:
1#地址:0x7fffc1dc621c,值:0
2#地址:0x7fffc1dc61d0,值:1000
3#地址:0x7fffc1dc621c,值:0

【问题讨论】:

  • 请注意 bind 复制其参数。
  • 对不起,我不太明白,因为我使用了 std::forward

标签: c++ function c++11 variadic-templates


【解决方案1】:

首先,您的Delegate 类的构造函数完全损坏。这是std::shared_ptr 的错误用法。你应该像下面这样修复:

// define
explicit Delegate(std::shared_ptr<WorkerType> wrk) : m_worker(std::move(wrk)) {}
//call
Delegate<SomeWorker> del(std::make_shared<SomeWorker>());

要将对象左值引用传递给std::bind,您必须使用std::ref

How to bind function to an object by reference?

当您将std::ref 结果传递给workerDo 时,WrkFunc 会打扰呼叫。

所以,你需要使用std::is_invocable_r_v重写类型检查

#include <functional>
#include <iostream>
#include <memory>
#include <type_traits>
template <class WorkerType> class Delegate {
public:

  explicit Delegate(std::shared_ptr<WorkerType> wrk) : m_worker(std::move(wrk)) {}

  template <typename Func, typename ...Args, std::enable_if_t<std::is_invocable_r_v<void, Func, WorkerType, Args...>, std::nullptr_t> = nullptr>
  void workerDo(Func func, Args && ...args) {
    auto fn = std::bind(func, m_worker.get(), std::forward<Args>(args)...);
    fn();
  }

private:
  std::shared_ptr<WorkerType> m_worker;
};

class SomeWorker {
public:
  SomeWorker() = default;

  void doSomething(int &a) {
    a = 1000;
    std::cout << "2# address: " << &a << ", value: " << a << std::endl;
  }
};

int main() {
  Delegate<SomeWorker> del(std::make_shared<SomeWorker>());
  int a = 0;
  std::cout << "1# address: " << &a << ", value: " << a << std::endl;
  del.workerDo(&SomeWorker::doSomething, std::ref(a));
  std::cout << "3# address: " << &a << ", value: " << a << std::endl;
  return 0;
}

https://wandbox.org/permlink/fnBThXw5Uh72JeGQ


有什么方法可以避免在任何地方使用 std::ref 调用 workerDo 吗?

使用下面的包装函数将解决。

template <class T, class U, std::enable_if_t<
    (std::is_lvalue_reference_v<T> ? std::is_lvalue_reference_v<U> : true) &&
    std::is_convertible_v<std::remove_reference_t<U>*, std::remove_reference_t<T>*>,
    std::nullptr_t
> = nullptr>
inline decltype(auto) forward_or_construct_reference_wrapper(U&& u)
{
    if constexpr(std::is_lvalue_reference_v<T>) {
        return std::reference_wrapper{std::forward<T>(u)};
    }
    else {
        return static_cast<T&&>(u);
    }
}

https://wandbox.org/permlink/wli9Dh9vXSjzBWMA

【讨论】:

  • 谢谢!顺便说一句,有什么方法可以避免在任何地方使用 std::ref 调用 workerDo 吗?
猜你喜欢
  • 1970-01-01
  • 2020-12-27
  • 2016-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多