std :: bind с функцией вариативного шаблона

Я пытаюсь написать общую фабрику 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;
}

В большинстве примеров аргументы с переменным числом аргументов всегда записываются как «Args && ...» в списке аргументов функций. но это не работает с привязкой, скомпилируйте сообщение об ошибке, подобное этому (clang-902.0.39.2)

ошибка: нет приемлемого преобразования из '__bind (&) (int &&, int &&), int, int>' в 'const ObjFactory :: CreatorFunc' (aka 'const function ()>') factory.registerCreator ("withArgs", std :: bind (create_obj, 1, 2));

После удаления "&&" все работает нормально

Но я не знаю почему?


person Charles    schedule 10.08.2018    source источник
comment
Это не объясняет, почему, но замена std::bind на лямбда (как обычно рекомендуется) работает должным образом. [](){ return create_obj<Derived>(1,2); }   -  person super    schedule 10.08.2018
comment
Если интересно, можно найти более простой пример проблемы OPs здесь.   -  person WhozCraig    schedule 10.08.2018
comment
std::bind проводите сеанс, если у вас нет C ++ 11, иначе вам следует использовать лямбда.   -  person Marek R    schedule 10.08.2018


Ответы (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&}.

Если вы вызовете его с rvalues ​​для аргументов (т.е. foo(1, 2)), тогда Args будет выведено как {int, int}, а типы значений в args станут {int&&, int&&}.


Это основы универсальных ссылок, так что теперь давайте посмотрим, что происходит, когда вы вызываете

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

Здесь вы не разрешили вывод аргументов шаблона, поэтому Args равно {int, int}, а foo, следовательно, ожидает аргументы типа {int&&, int&&}. Однако значения 1 и 2 копируются в объект привязки и передаются вызываемому объекту как lvalues ​​. Ссылки Rvalue не могут быть привязаны к lvalue, поэтому вызов не может быть скомпилирован.


Чтобы сделать это правильно, используйте лямбда вместо std::bind:

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

С лямбда-выражением вывод аргументов шаблона работает как обычно, а 1 и 2 остаются значениями r. Все работает так, как ожидалось, и универсальные ссылки выполняют свою работу, потому что они используются в предполагаемом контексте.

person Miles Budnek    schedule 10.08.2018

при использовании ссылок пересылки вам нужно позволить выводить аргументы, иначе вы не сможете их перенаправить идеально, потому что вы не будете знать реальный тип, который будет использоваться для них.

в вашем случае вы указали тип create_obj<Derived, int, int>, функция будет создана для std::shared_ptr<Derived> create_obj(int&&, int&&) и не будет гибкой, она будет просто принимать r-значение int.

и вы назначили вызываемый объект 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&) будет работать в этом случае, но все равно не будет иметь гибкости пересылки ссылок.

реальное решение - использовать лямбда.

person Tyker    schedule 10.08.2018
comment
Понятно! Большое спасибо! - person Charles; 11.08.2018