Шаблон Variadic как параметры для std::function

Я хочу построить структуру, которая позволит мне вызывать функции-члены с неопределенным количеством параметров. Пока что я написал что-то вроде этого

template<typename Class, typename Return, typename ... Args>
struct Caller
{
private:
    std::function<Return(Args ...)> callerFunction;

    Caller() = delete;
    Caller(const Caller&) = delete;
    Caller(Caller&&) = delete;
    Caller& operator=(const Caller&) = delete;

public:
    ~Caller() = default;

    Caller(Class& instance, Return(Class::*function)(Args ...))
    {
        callerFunction = [&instance, function](Args... args)
        {
            return (instance.*function)(args ...);
        };
    }

    Return operator() (Args ... args)
    {
        return callerFunction(args ...);
    }
};

Боюсь, я не могу обойти тот факт, что я не могу объявить переменную std::function как std::function<Return<Args&& ...)> callerFunction

Когда я пытаюсь сделать это, компилятор говорит, что он не может преобразовать из int в int&& (если, например, параметры равны ints), поэтому я предполагаю, что функция видит Args&& ... как пакет параметров ссылок rvalue. Я прав?

Есть ли обходной путь?

Редактировать: Хорошо, я неправильно объявлял функцию внутри конструктора Caller.

Неверный путь --> Caller(Class& instance, Return(Class::*function)(Args&& ...))

Верный путь --> Caller(Class& instance, Return(Class::*function)(Args ...))

(Я думаю) правильная реализация

template<typename Class, typename Return, typename ... Args>
struct Caller
{
private:
    std::function<Return(Args&& ...)> callerFunction;

    Caller() = delete;
    Caller(const Caller&) = delete;
    Caller(Caller&&) = delete;
    Caller& operator=(const Caller&) = delete;

public:
    ~Caller() = default;

    Caller(Class& instance, Return(Class::*function)(Args ...))
    {
        callerFunction = [&instance, function] (Args&&... args)
        {
            return (instance.*function)(std::forward<Args>(args) ...);
        };
    }

    Return operator() (Args&& ... args)
    {
        return callerFunction(std::forward<Args>(args) ...);
    }
};

Теперь вопрос: почему мне все равно нужно объявлять функцию без двойного &?


person Astinog    schedule 29.09.2017    source источник
comment
Какой компилятор вы используете? Он работает в Visual Studio 2017.   -  person Outshined    schedule 29.09.2017
comment
И покажите нам, как вы создаете экземпляр вызывающего объекта.   -  person Outshined    schedule 29.09.2017
comment
Я использую Visual Studio 2013 и делаю что-то вроде этого Caller<A, bool, int, int, int> caller(a, &A::foo);. В любом случае, я бы изменил структуру, используя std::forward, когда это необходимо.   -  person Astinog    schedule 29.09.2017
comment
Я пытаюсь использовать VS2017, и у меня такая же проблема. Полный код, который я использую, находится здесь pastebin.com/99QFwfA8.   -  person Astinog    schedule 29.09.2017


Ответы (2)


Когда вы определяете свой шаблон класса следующим образом:

template <typename T>
struct A {
 A(T&& param)
}

А затем создайте экземпляр:

A<int> myInstance(someIntVariable);

Он не будет компилироваться. Причина в том, что тип T явно указан вами (как int в A<int>), а параметр конструктора вашего класса больше не T&&, а int&&, поэтому он больше не является универсальной ссылкой (которая принимает как ссылки lvalue, так и ссылки rvalue) , но обычная ссылка на rvalue.

Затем, если вы передадите ему какое-то целое число, произойдет ошибка несоответствия типа, потому что вы передаете обычную переменную, когда ожидается ссылка на rvalue.

В вашем примере вы явно определили сигнатуры функций, поэтому применимо то же самое - конструктор ожидает функцию, принимающую ссылки rvalue на Args..., но это неверно.

Я думаю, что это лучше объяснить в этом вопросе< /сильный>

person Outshined    schedule 29.09.2017
comment
Спасибо за пояснение, теперь это так очевидно! Мое единственное сомнение: мне НЕ нужно использовать переадресацию в любом случае, потому что я указываю типы каждой переменной, которую я передам функции, верно? Мне не нужно использовать переадресацию даже в функции operator(), которую я использую для вызова функции-члена с аргументами, верно? - person Astinog; 29.09.2017
comment
@Astinog Верно. std::forward следует использовать только с универсальными ссылками. Его единственная цель - передать параметр (вперед) другой функции без изменения его типа, поэтому, если T&& будет передано lvalue, будет передано lvalue, если rvalue, то rvalue. Вы можете узнать больше об этом, если вы погуглите о идеальной переадресации на С++, на эту тему есть много ресурсов :) - person Outshined; 29.09.2017

int&& не то же самое, что int. Если ваш метод принимает int, он не принимает int&&. Они разных типов.

Преобразование между различными сигнатурами указателя на функцию-член не выполняется.

Ссылки на пересылку — это не волшебство. Они зависят от правил дедукции (и Args&&... выше не используется в дедуцируемом контексте) и от правил свертывания ссылок.

Таким образом, ваш

Return operator() (Args&& ... args)

тоже неправильно. Если Args... равно int, то вы не сможете вызвать вышеуказанное с int a; blah.operator()(a), так как a не будет привязано к int&&.

Честно говоря, весь ваш тип должен быть выброшен и заменен на std::function<Return(Args...)>. Class — это бесполезное сужение того, что делает функция, и ваши обертки тоже мало что добавляют.

Пользователи могут просто [&a](auto&&...args)->decltype(auto){ return a.foo(decltype(args)(args)...); }, если им действительно нужно воспроизвести ваш конструктор, или использовать std::bind( &A::foo, std::ref(a) )

person Yakk - Adam Nevraumont    schedule 29.09.2017