【问题标题】:std::bind with variadic template functionstd::bind 与可变参数模板函数
【发布时间】:2019-01-18 08:15:54
【问题描述】:

我正在尝试使用可变参数模板编写一个通用 obj 工厂来调用各种类的构造函数。代码如下:

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <unordered_map>

template<typename T>
class ObjFactory {
public:
    typedef std::shared_ptr<T>              ObjPtr;
    typedef std::function<ObjPtr(void)>     CreatorFunc;
public:
    void registerCreator(const std::string &name, const CreatorFunc &creator)
    { m_dictCreator[name] = creator; }

    ObjPtr getObj(const std::string &name) const
    {
        auto it = m_dictCreator.find(name);
        return (it == m_dictCreator.end() ? nullptr : (it->second)());
    }
private:
    std::unordered_map<std::string, CreatorFunc>   m_dictCreator;
};


using namespace std;

struct Base {
    virtual ~Base() {}
    virtual void greet() const
    { cout << "I am Base" << endl; }
};

struct Derived : Base {
    Derived() : x(0), y(0) {}
    Derived(int a, int b) : x(a), y(b) {}
    int x, y;

    virtual void greet() const
    { cout << "I am Derived x = " << x << " y = " << y << endl; }
};

template<typename T, typename... Args>
std::shared_ptr<T> create_obj(Args... args)  // This OK
// std::shared_ptr<T> create_obj(Args&&... args) // WRONG
{ return std::make_shared<T>(std::forward<Args>(args)...); }

int main()
{
    ObjFactory<Base> factory;
    factory.registerCreator("default", create_obj<Derived>);
    factory.registerCreator("withArgs", std::bind(create_obj<Derived, int, int>, 1, 2));

    do {
        auto pObj = factory.getObj("default1");
        if (pObj) { pObj->greet(); }
    } while (0);

    do {
        auto pObj = factory.getObj("withArgs");
        if (pObj) { pObj->greet(); }
    } while (0);

    return 0;
}

在大多数示例中,可变参数 arg 在函数 arg 列表中总是这样写“Args&&...”。但这不适用于绑定,像这样编译错误消息(clang-902.0.39.2)

错误:从 '__bind 没有可行的转换 (&)(int &&, 整数 &&), int, int>' to 'const ObjFactory::CreatorFunc' (又名'const function ()>') factory.registerCreator("withArgs", std::bind(create_obj, 1, 2));

去掉“&&”后就可以了

但我不知道为什么?

【问题讨论】:

  • 这不能解释为什么,但是用 lambda 替换 std::bind(通常是推荐的)按预期工作。 [](){ return create_obj&lt;Derived&gt;(1,2); }
  • 如果有兴趣,OPs 问题的最小案例can be found here
  • std::bind 在你没有 C++11 时进行降神会,否则你应该使用 lambda。

标签: c++ variadic-templates stdbind


【解决方案1】:

通用引用仅在推断的上下文中有效。当显式指定模板参数时,它们不会按预期运行。

给定函数模板

template <typename... Args>
void foo(Args&&... args) {}

还有电话

int a = 1;
int b = 2;
foo(a, b);

Args 将被推断为{int&amp;, int&amp;}。应用了参考折叠,int&amp; &amp;&amp; 被折叠为 int&amp;。这意味着args 中的值的类型是{int&amp;, int&amp;}

如果您使用参数的右值(即foo(1, 2))调用它,那么Args 将被推导出为{int, int},并且args 中值的类型变为{int&amp;&amp;, int&amp;&amp;}


这是通用引用的基础,现在让我们看看调用时会发生什么

auto fn = std::bind(foo<int, int>, 1, 2);
fn();

在这里,您不允许进行模板参数推导,因此Args{int, int},因此foo 期待{int&amp;&amp;, int&amp;&amp;} 类型的参数。 12 的值被复制到绑定对象中并作为 lvalues 传递给可调用对象。右值引用无法绑定到左值,因此调用无法编译。


使这项工作正常工作的方法是使用 lambda 而不是 std::bind:

auto fn = []() { foo(1, 2); };
fn();

使用 lambda,模板参数推导正常工作,12 保持右值。一切都按预期工作,通用引用完成了它们的工作,因为它们被用于推断的上下文中。

【讨论】:

    【解决方案2】:

    使用转发引用时,您需要推导出参数,否则您无法完善转发它们,因为您不知道用于它们的真实类型。

    在你的情况下,你给了一个类型 create_obj&lt;Derived, int, int&gt; 该函数将被实例化为 std::shared_ptr&lt;Derived&gt; create_obj(int&amp;&amp;, int&amp;&amp;) 并且没有灵活性,它只会采用 r-value int。

    并且您将可调用对象分配给const CreatorFunc&amp;,因此闭包为const,并且您的可调用对象无法接收const 参数

    create_obj&lt;Derived, int, int&gt; 替换为 create_obj&lt;Derived, const int&amp;, const int&amp;&gt; 会导致 create_obj 被实例化为 std::shared_ptr&lt;Derived&gt; create_obj(const int&amp;, const int&amp;) 在这种情况下会起作用,但仍然没有转发引用的灵活性。

    真正的解决方案是使用 lambda。

    【讨论】:

    • 知道了!非常感谢!
    猜你喜欢
    • 2019-04-30
    • 1970-01-01
    • 2020-02-15
    • 2023-04-07
    • 2014-01-09
    • 1970-01-01
    • 2016-04-26
    • 2018-11-26
    • 1970-01-01
    相关资源
    最近更新 更多