Вызов указателя функции шаблона с нулевым аргументом с вариативным аргументом шаблона?

Вот фрагмент кода из сообщения по адресу Functional C++ блог, описывающий, как можно реализовать обобщенное вычисление функций.

Мой вопрос: как вы можете объявить указатель функции шаблона f как R(C::*f)() без аргументов и при этом иметь возможность вызывать его с помощью Args…?

// functions, functors, lambdas, etc.
template<
    class F, class... Args,
    class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type,
    class = typename std::enable_if<!std::is_member_object_pointer<F>::value>::type
    >
auto eval(F&& f, Args&&... args) -> decltype(f(std::forward<Args>(args)...))
{
    return f(std::forward<Args>(args)...);
}

// const member function
template<class R, class C, class... Args>
auto eval(R(C::*f)() const, const C& c, Args&&... args) -> R
{
    return (c.*f)(std::forward<Args>(args)...);
}

template<class R, class C, class... Args>
auto eval(R(C::*f)() const, C& c, Args&&... args) -> R
{
    return (c.*f)(std::forward<Args>(args)...);
}

// non-const member function
template<class R, class C, class... Args>
auto eval(R(C::*f)(), C& c, Args&&... args) -> R
{
    return (c.*f)(std::forward<Args>(args)...);
}

// member object
template<class R, class C>
auto eval(R(C::*m), const C& c) -> const R&
{
    return c.*m;
}

template<class R, class C>
auto eval(R(C::*m), C& c) -> R&
{
    return c.*m;
}

struct Bloop
{
    int a = 10;
    int operator()(){return a;}
    int operator()(int n){return a+n;}
    int triple(){return a*3;}
};

int add_one(int n)
{
    return n+1;
}

int main()
{
    Bloop bloop;

    // free function
    std::cout << eval(add_one,0) << "\n";

    // lambda function
    std::cout << eval([](int n){return n+1;},1) << "\n";

    // functor
    std::cout << eval(bloop) << "\n";
    std::cout << eval(bloop,4) << "\n";

    // member function
    std::cout << eval(&Bloop::triple,bloop) << "\n";

    // member object
    eval(&Bloop::a,bloop)++; // increment a by reference
    std::cout << eval(&Bloop::a,bloop) << "\n";

    return 0;
}

Например, когда я пытаюсь:

struct Bloop
{
    int a = 10;
    int operator()(){return a;}
    int operator()(int n){return a+n;}
    int triple(){return a*3;}
    int foo(int n) {return n;}
};

template <typename R, typename C, typename... Args>
void eval (R(C::*func)(), C& c, Args... args) {
    (c.*func)(args...);
}

int main()
{
    Bloop bloop;

    eval(&Bloop::foo, bloop, 5);

    return 0;
}

Я получаю эту ошибку:

main.cpp: In function 'int main()':
main.cpp:27:31: error: no matching function for call to 'eval(int (Bloop::*)(int), Bloop&, int)'
     eval(&Bloop::foo, bloop, 5);
                               ^
main.cpp:27:31: note: candidate is:
main.cpp:19:6: note: template<class R, class C, class ... Args> void eval(R (C::*)(), C&, Args ...)
 void eval (R(C::*func)(), C& c, Args... args) {
      ^
main.cpp:19:6: note:   template argument deduction/substitution failed:
main.cpp:27:31: note:   candidate expects 1 argument, 2 provided
     eval(&Bloop::foo, bloop, 5);
                               ^

И если я объявлю func как R(C::*func)(int), он скомпилируется.


person Benji Mizrahi    schedule 10.12.2014    source источник
comment
Что не так с выводом параметров этой функции со вторым пакетом параметров (или даже с тем же), например void eval (R(C::*func)(Params...), C& c, Args... args) ?   -  person Piotr Skotnicki    schedule 10.12.2014
comment
Да, вот так это работало. Но почему это не требуется в приведенном выше фрагменте кода из блога Functional C++?   -  person Benji Mizrahi    schedule 10.12.2014
comment
Что вы подразумеваете под это не требуется в записи блога? Я думаю, что просто авторы не пытались создать экземпляр этого шаблона функции с функциями-членами с указателем на ненулевой параметр, как это сделали вы. также помните, что пакет параметров также может быть пустым   -  person Piotr Skotnicki    schedule 10.12.2014
comment
Нет, в main() он вызывает eval(bloop,4) (с аргументом int)   -  person Benji Mizrahi    schedule 10.12.2014
comment
Петр прав. Я только что посмотрел на этот код. Функция-член, которую они передают, является bloop, которая не принимает аргументов. Если вы измените это, чтобы принять один аргумент, компиляция завершится неудачно, как в вашем примере.   -  person Jay Miller    schedule 10.12.2014
comment
eval(bloop, 4) вызывает перегрузку Functor, которая принимает аргумент шаблона, не использующий сигнатуру функции.   -  person Jay Miller    schedule 10.12.2014


Ответы (2)


Код в статье блога неверный (или, по крайней мере, неполный); он работает только для функций без аргументов. Вы могли бы написать eval более правильно так:

template<class R, class C, class... T, class... Args>
auto eval(R(C::*f)(T...), C& c, Args&&... args) -> R
{
    return (c.*f)(std::forward<Args>(args)...);
}

Обратите внимание на пакет параметров T... для аргументов указателя на тип функции-члена. Это пакет типов, отличный от Args&&..., потому что эти два пакета могут быть выведены по-разному.

person ecatmur    schedule 10.12.2014

Этот код можно было бы одновременно сделать более простым и универсальным, избегая анализа типов «указатель на функцию-член» и «указатель на данные-член» и просто принимая все, для чего вызовы четко определены:

#define RETURNS(...) \
  -> decltype(__VA_ARGS__) { \
    return (__VA_ARGS__); \
  }

// Function object type
template<class F, class... Args>
auto eval(F&& f, Args&&... args)
RETURNS(std::forward<F>(f)(std::forward<Args>(args)...))

// pointer to member function, object reference
template<class PMF, class C, class... Args>
auto eval(PMF&& pmf, C&& c, Args&&... args)
RETURNS((std::forward<C>(c).*std::forward<PMF>(pmf))(std::forward<Args>(args)...))

// pointer to member data, object reference
template<class PMD, class C>
auto eval(PMD&& pmd, C&& c)
RETURNS(std::forward<C>(c).*std::forward<PMD>(pmd))

пока мы на этом, мы можем также поддерживать пропущенные случаи указателя на члены с указателями на объекты в дополнение к ссылкам на объекты для полноты, особенно учитывая, что пример кода требует, чтобы они оценивали eval(&Bloop::a,&bloop)++:

// pointer to member data, object pointer
template<class PMD, class P>
auto eval(PMD&& pmd, P&& p)
RETURNS((*std::forward<P>(p)).*std::forward<PMD>(pmd))

// pointer to member function, object pointer
template<class PMF, class P, class... Args>
auto eval(PMF&& pmf, P&& p, Args&&... args)
RETURNS(((*std::forward<P>(p)).*std::forward<PMF>(pmf))(std::forward<Args>(args)...))

ДЕМО

(Хорошо, возможно, слово «проще» было неудачным выбором. «Более лаконичный» или «краткий», вероятно, было бы более точным.)

person Casey    schedule 10.12.2014