简体   繁体   中英

std::bind with variadic template function

I'm trying to write a generic obj factory using variadic template to call constructors of various classes. code as follow:

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

In most examples, variadic arg always write like this "Args&&..." in function arg list. but this does not work with bind, compile error msg like this (clang-902.0.39.2)

error: no viable conversion from '__bind (&)(int &&, int &&), int, int>' to 'const ObjFactory::CreatorFunc' (aka 'const function ()>') factory.registerCreator("withArgs", std::bind(create_obj, 1, 2));

After removing the "&&", it works OK

But I don't know why?

Universal references only work in a deduced context. They do not function as you expect when the template parameters are explicitly specified.

Given the function template

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

And the call

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

Args will be deduced to be {int&, int&} . Reference collapsing is applied, and int& && gets collapsed to just int& . That means the types of the values in args are {int&, int&} .

If you call it with rvalues for arguments (ie foo(1, 2) ) then Args will be deduced to be {int, int} and the types of the values in args become {int&&, int&&} .


That's the basics of universal references, so now let's look at what happens when you call

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

Here, you've not allowed template argument deduction to take place so Args is {int, int} and foo is therefore expecting arguments of type {int&&, int&&} . The values 1 , and 2 are copied into the bind object and passed to the callable as lvalues though. Rvalue references can't bind to lvalues, and so the call fails to compile.


The way to make this work correctly is to use a lambda instead of std::bind :

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

With a lambda, template argument deduction works as normal, and 1 , and 2 remain rvalues. Everything works as expected and the universal references do their job because they're being used in a deduced context.

when using forwarding references you need to let argument being deduced otherwise you can't perfect forward them because you won't know the real type to use for them.

in your case you gave a type create_obj<Derived, int, int> the function will be instantiated to std::shared_ptr<Derived> create_obj(int&&, int&&) and have no flexibility it will just be taking r-value int.

and you assigned the callable to a const CreatorFunc& and so the closure was const and your callable couldn't receive const arguments

replacing create_obj<Derived, int, int> by create_obj<Derived, const int&, const int&> which cause create_obj to be instantiated as std::shared_ptr<Derived> create_obj(const int&, const int&) will work in this case but would still not have the flexibility of forwarding references.

the real solution is to use lambda.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM