std :: bind与可变参数模板函数

时间:2018-08-10 17:10:14

标签: c++ variadic-templates stdbind

我正在尝试使用可变参数模板编写通用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)编译错误msg

  

错误:无法从'__bind进行转换   (&)(int &&,int         &&),int,int>'到'const ObjFactory :: CreatorFunc'(又名'const function()>')       factory.registerCreator(“ withArgs”,std :: bind(create_obj,1,2));

删除“ &&”后,效果很好

但是我不知道为什么吗?

2 个答案:

答案 0 :(得分:2)

通用引用仅在推论的上下文中起作用。当显式指定模板参数时,它们无法按预期运行。

给出功能模板

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

还有电话

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

Args将被推导为{int&, int&}。应用引用折叠,int& &&折叠为int&。这意味着args中值的类型为{int&, int&}

如果使用带有参数的右值来调用它(即foo(1, 2)),则将推论Args{int, int}args中值的类型变为{{ 1}}。


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

{int&&, int&&}

在这里,您不允许进行模板参数推导,因此auto fn = std::bind(foo<int, int>, 1, 2); fn(); Args,因此{int, int}期望类型为foo的参数。值{int&&, int&&}1被复制到绑定对象中,并作为 lvalues 传递给可调用对象。右值引用不能绑定到左值,因此调用无法编译。


正确执行此操作的方法是使用lambda代替2

std::bind

使用lambda时,模板参数推导正常进行,并且auto fn = []() { foo(1, 2); }; fn(); 1仍然是右值。一切都按预期工作,通用引用也能发挥作用,因为它们是在推论上下文中使用的。

答案 1 :(得分:0)

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

在您的情况下,您输入了create_obj<Derived, int, int>类型 该函数将实例化为std::shared_ptr<Derived> create_obj(int&&, int&&),并且没有灵活性,它只会采用r值整数。

并将可调用对象分配给const CreatorFunc&,因此闭包为const,而您的可调用对象无法接收const自变量

在这种情况下,

create_obj<Derived, int, int>替换create_obj<Derived, const int&, const int&>会导致create_obj实例化为std::shared_ptr<Derived> create_obj(const int&, const int&),但仍然没有转发引用的灵活性。

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